【学习笔记】逆熵法

NCC79601

2019-08-16 21:13:49

Personal

# 背景 有$n$个数,但并不给你,而是给了你$m=\frac12 n\cdot(n-1)$个数($n\leq300$),代表它们两两的和。求这$n$个数。 # 分析 这里要运用**逆熵**的思路。名字是自己瞎~~jb~~取的,但是我感觉这个名字非常贴切。 **逆熵**:通过一定的方式把杂乱的信息整理为有逻辑顺序的信息。通常的方式也就是排序等等。在面对一大堆不知道该如何处理的信息的时候,就应该通过一些假设与限制,把整个问题的熵值降低,从而变得能够在可接受时间内解决。 回到这道题,$m$个数是杂乱无章的,也就是说我们根本不知道这些数和原序列的对应关系。 开始使用**逆熵法**:假设这$n$个数分别为$a_1,a_2,\cdots,a_n$,并且它们按照升序排列;再把这$m$个两两和$s_1,s_2,\cdots,s_m$按照升序排列。这样处理以后,整个系统的熵值降低,并且出现了第一个由假设引入的逻辑关系:由于$a_1<a_2<a_3<\cdots<a_n$,因此$a_1+a_2<a_1+a_3<\cdots<a_{n-1}+a_n$。再观察,$s_1<s_2<\cdots<s_m$,也就是说排序以后得到了: $$a_1+a_2=s_1,$$ $$a_1+a_3=s_2.$$ 因此只要获知$a_2+a_3$的值,它们就构成了三个三元一次方程,即可解得$a_1,a_2,a_3$。可是$a_2+a_3$的位置无法确定,因为$a_1<a_2<a_3<a_4<\cdots<a_n$,所以无法比较$a_1+a_i(i\geq4)$和$a_2+a_3$的大小。因此$a_2+a_3$有可能出现在$s_3\sim s_n$中的每一个数,也就是说这里的熵值无法继续降低,因此开始枚举。从$s_3\sim s_n$中挑选出一个数作为$a_2+a_3$,那么: $$a_2+a_3=s_x.$$ 联立三式解得: $$\left\{\begin{aligned}a_1 &= \frac{s_1+s_2-s_x}2,\\a_2 &= s_1-a_1,\\a_3 &= a_2-a_1.\end{aligned}\right.$$ 所以根据$s_1+s_2-s_x$的奇偶性就可以判断这种情况是否存在($a_1,a_2,a_3\in N^+$)。此后,将$a_1+a_2,a_1+a_3,a_2+a_3$从$s$序列中删除。那么依据我们的假设,剩下来的最小的$s$就是$a_1+a_4$了,同样可以解得$a_4$;然后把$a_i+a_4(i<4)$给删去,剩下来的最小的$s$就是$a_i+a_5\cdots$以此类推,便可解得所有数的值。 总结一下,算法流程如下: 1. 枚举$a_2+a_3$,解得$a_1,a_2,a_3$; 2. 重复以下步骤,直到解得所有数的值:$(i=4\sim n)$ ① 找到当前最小的$s_x$,根据假设可知$s_x=a_1+a_i$,由此可以解得$a_i$; ② 在$s$序列中把所有当前利用$a$序列能够确定的$s$值删去,具体为把$a_j+a_i(j<i)$全部删除,代码中可以用 multiset 实现。 ```cpp #include <bits/stdc++.h> using namespace std; const int MAXN = 310; const int MAXM = 5e5; struct sequence { int a[MAXN]; bool operator < (const sequence &rhs) const { return a[1] > rhs.a[1]; } // 其实这儿有点问题,字典序不是这样搞的 =w= } seq[MAXN]; int n, m, s[MAXM], ans = 0; void solve(int &a1, int &a2, int &a3, int s1, int s2, int s3) { a1 = (s1 + s2 - s3) >> 1; a2 = s1 - a1; a3 = s2 - a1; } void start_from(int s3) { multiset<int> mset; for(int i = 1; i <= m; i++) mset.insert(s[i]); int a[MAXN]; solve(a[1], a[2], a[3], s[1], s[2], s3); mset.erase(mset.find(a[1] + a[2])); mset.erase(mset.find(a[1] + a[3])); mset.erase(mset.find(a[2] + a[3])); for(int i = 4; i <= n; i++) { a[i] = * mset.begin() - a[1]; for(int j = 1; j < i; j++) { if(mset.find(a[j] + a[i]) == mset.end()) return; mset.erase(mset.find(a[j] + a[i])); } } ans++; memcpy(seq[ans].a, a, sizeof(a)); } int main() { scanf("%d", &n); m = n * (n - 1) / 2; for(int i = 1; i <= m; i++) scanf("%d", &s[i]); sort(s + 1, s + m + 1); for(int i = 3; i <= n; i++) { if(s[i] == s[i - 1]) continue; if((s[1] + s[2] - s[3]) % 2 == 0) start_from(s[i]); } sort(seq + 1, seq + ans + 1); printf("%d\n", ans); for(int i = 1; i <= ans; i++) { for(int j = 1; j <= n; j++) printf("%d ", seq[i].a[j]); putchar('\n'); } return 0; } ``` # 总结 很多时候面对这种数据繁多、逻辑关系不明朗的题目,我会感觉无从下手。这是因为我总是在拿算法套路来尝试,又因为出题人故意隐藏,所以迷失了方向。其实,只要合理地作出假设,用各种方法**将系统的熵值降低**,然后再根据已有的条件进行组合和演绎,就能发现一条明朗的思路。 这种从混沌之中寻求解题思路的方法,可以迅速地为系统建立模型,由此进行抽象的演算。所以,不失把逆熵法作为题目的突破方法。