题解 P3959 【宝藏】

_rqy

2017-11-13 18:02:33

Solution

qwq首先,prim是错误的,可以被这组数据卡掉:[数据](https://paste.ubuntu.com/25953020/) 然后,据说正解$O(4^n)$,而我的做法是$O(n^33^n)$,渐进意义上小但是$n \leq 12$的时候大。 然后,我的常数小至$\frac{1}{27}$,极限数据(就是12个点之间全有边)在sd的辣鸡WinXp上跑了0.8秒。luogu自测过了。 我的想法是dp。 状态: $f_{i,j,S}(j \not\in S)$,表示从起点到节点j的距离为i,我现在要从j开始挖边,挖通集合S的最小代价。 转移时,我枚举从节点j打出的第一条边,假设它是j->k,再枚举从k要打到的子集$S_2\subset S$,那么 $$f_{i, j, S} = \min_{k \in S_2 \subset S} (d[j][k] * (i + 1) + f_{i + 1, k, S_2 - \{k\}} + f_{i, j, S - S_2})$$ 最后取所有$f_{0,i,U-{i})$的最小值即可,其中$U$是全集。 时间复杂度。。。这个太难算,用了好多二项式系数恒等式,这里略去。 程序中先枚举的$S_2$后枚举的$k \in S_2$,并且预处理了每个集合的最小元素以加快枚举。 代码: ```cpp #include <algorithm> #include <cstdio> #include <cstring> namespace rqy{ const int N = 13; const int INF = 0x3f3f3f3f; int f[N][N][1 << N]; int siz[1 << N], ttt[1 << N]; int d[N][N]; void main() { int n, m, x, y, z; scanf("%d%d", &n, &m); memset(f, 0x3f, sizeof f); memset(d, 0x3f, sizeof d); while (m--) { scanf("%d%d%d", &x, &y, &z); --x; --y; d[y][x] = d[x][y] = std::min(d[x][y], z); } int up = 1 << n; for (int i = 1; i < up; ++i) siz[i] = siz[i & (i - 1)] + 1; for (int i = 0; i < n; ++i) ttt[1 << i] = i; for (int i = 1; i < up; ++i) ttt[i] = ttt[i & -i]; for (int i = 0; i < n; ++i) f[n - 1][i][0] = 0; for (int j = n - 2; ~j; --j) for (int i = 0; i < n; ++i) { f[j][i][0] = 0; for (int S = 1; S < up; ++S) if (((~S >> i) & 1) && (siz[S] <= n - j - 1)) for (int S2 = S; S2; S2 = (S2 - 1) & S) if (f[j][i][S & ~S2] < f[j][i][S]) { int tmp = S2; for (int k = ttt[tmp]; tmp; k = ttt[tmp &= ~(1 << k)]) if (d[i][k] < INF) f[j][i][S] = std::min(f[j][i][S], (j + 1) * d[i][k] + f[j + 1][k][S2 & ~(1 << k)] + f[j][i][S & ~S2]); } } int ans = INF; for (int i = 0; i < n; ++i) ans = std::min(ans, f[0][i][(up - 1) & ~(1 << i)]); printf("%d\n", ans); } }; int main() { rqy::main(); return 0; } ```