Luogu 3959 [NOIP2017] 宝藏
NOIP2017最后一道题
挺难想的状压dp。
受到深度的条件限制,所以一般的状态设计带有后效性,这时候考虑把深度作为一维,这样子可以保证所有状态不重复计算一遍。
神仙预处理:先处理出一个点连到一个集合所需要的最小代价,然后再处理出一个集合连到一个集合所需要的最小代价
设$g_{s, t}$表示从s集合连到t集合的最小代价, $f_{i, j}$表示当前深度为i,挖到集合s的最小代价,有转移:
$f_{i, s} = min(g_{s, t} * (i - 1) + f_{i - 1, t})$ t是s的子集
最后的答案 $ans = min(f_{i, maxS})$ $(0<i<n)$
可以发现这样子最优答案一定会被计算到。
时间复杂度$O(3^{n} * 2 ^ {n} * n)$.
Code:
#include <cstdio> #include <cstring> using namespace std; const int N = 15; const int S = (1 << 12) + 5; const int inf = 0x3f3f3f3f; int n, m, e[N][N], h[N][S], g[S][S], f[N][S]; inline void read(int &X) { X = 0; char ch = 0; int op = 1; for(; ch > '9' || ch < '0'; ch = getchar()) if(ch == '-') op = -1; for(; ch >= '0' && ch <= '9'; ch = getchar()) X = (X << 3) + (X << 1) + ch - 48; X *= op; } inline int min(int x, int y) { return x > y ? y : x; } inline void chkMin(int &x, int y) { if(y < x) x = y; } int main() { read(n), read(m); memset(e, 0x3f, sizeof(e)); for(int x, y, v, i = 1; i <= m; i++) { read(x), read(y), read(v); e[x][y] = min(e[x][y], v); e[y][x] = min(e[y][x], v); } /* for(int i = 1; i <= n; i++, printf("\n")) for(int j = 1; j <= n; j++) printf("%d ", e[i][j]); */ for(int i = 1; i <= n; i++) { for(int s = 1; s < (1 << n); s++) { h[i][s] = inf; if(!(s & (1 << (i - 1)))) for(int j = 1; j <= n; j++) if(s & (1 << (j - 1))) chkMin(h[i][s], e[i][j]); } } for(int s = 1; s < (1 << n); s++) { for(int t = s & (s - 1); t; t = s & (t - 1)) { int x = s ^ t; for(int i = 1; i <= n; i++) if(x & (1 << (i - 1))) g[s][t] = min(g[s][t] + h[i][t], inf); } } memset(f, 0x3f, sizeof(f)); for(int i = 1; i <= n; i++) f[1][1 << (i - 1)] = 0; for(int i = 2; i <= n; i++) { for(int s = 1; s < (1 << n); s++) { for(int t = s & (s - 1); t; t = s & (t - 1)) { int tmp; if(g[s][t] != inf) tmp = g[s][t] * (i - 1); else tmp = inf; if(f[i - 1][t] != inf) chkMin(f[i][s], f[i - 1][t] + tmp); } } } int ans = inf; for(int i = 1; i <= n; i++) chkMin(ans, f[i][(1 << n) - 1]); printf("%d\n", ans); return 0; }