[NOIP2017] 宝藏 【树形DP】【状压DP】
题目分析:
这个做法不是最优的,想找最优解请关闭这篇博客。
首先容易想到用$f[i][S][j]$表示点$i$为根,考虑$S$这些点,$i$的深度为$j$情况的答案。
转移如下:$$ f[i][S][j] = min(w(i,k)*(j+1)+f[k][S_0][j+1]+f[i][S-S_0][j]) $$
其中$ S != {i} $且$ S_0 \subsetneqq S $且$ k \in S_0 $
$f[i][S][j] = 0$其中$S={i}$
这样已经可以通过了,时间复杂度是$O(n^3*3^n)$,原因是冗余状态太多。
但我们注意到对于每个集合我们都要遍历所有的为根的情况,用$g[i][S][j]$存储集合$S$中为根的最小值,可以优化到$O(n^2*3^n)$。
代码:
1 #include<bits/stdc++.h> 2 using namespace std; 3 4 int n,m; 5 6 int g[13][13]; 7 8 int f[13][1<<12][13],gi[13][1<<12][13]; 9 10 void read(){ 11 scanf("%d%d",&n,&m); 12 memset(g,0x3f,sizeof(g)); 13 for(int i=1;i<=m;i++){ 14 int x,y,v; scanf("%d%d%d",&x,&y,&v); 15 if(g[x][y] > v) g[x][y] = g[y][x] = v; 16 } 17 } 18 19 void paint(int now,int S,int j){ 20 for(int i=1;i<=n;i++){ 21 if((1<<i-1)&S) continue; 22 gi[i][S][j] = min(1ll*gi[i][S][j],f[now][S][j]+1ll*g[now][i]*j); 23 } 24 } 25 26 void work(){ 27 memset(f,0x3f,sizeof(f));memset(gi,0x3f,sizeof(gi)); 28 for(int i=n-1;i>=0;i--){ 29 for(int j=1;j<=n;j++){ 30 f[j][1<<j-1][i] = 0; 31 paint(j,1<<j-1,i); 32 } 33 for(int S=0;S<(1<<n);S++){ 34 int zeta = __builtin_popcount(S); 35 if(zeta > n-i || zeta <= 1) continue; 36 for(int k=1;k<=n;k++){ 37 if(!((1<<k-1)&S)) continue; 38 for(int S1 = S;S1;S1 = (S1-1)&S){ 39 f[k][S][i] = min(gi[k][S1][i+1]+f[k][S-S1][i],f[k][S][i]); 40 } 41 paint(k,S,i); 42 } 43 } 44 } 45 int ans = 2147483647; 46 for(int i=1;i<=n;i++){ 47 ans = min(ans,f[i][(1<<n)-1][0]); 48 } 49 printf("%d",ans); 50 } 51 52 int main(){ 53 read(); 54 work(); 55 return 0; 56 }