[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 }

 

posted @ 2018-06-21 10:59  menhera  阅读(270)  评论(0编辑  收藏  举报