BZOJ 2595: [Wc2008]游览计划(斯坦纳树)
http://www.lydsy.com/JudgeOnline/problem.php?id=2595
题意:
思路:
斯坦纳树和最小生成树是差不多的,斯坦纳树是将给定点连接起来的最小代价,可以额外增加点。主要是通过状压dp和spfa来做。
f [ i ] [ j ] [ now ] 表示以$(i,j)$为根节点并且状态为now时的最小代价。
它有两个状态转移方程:
f[i][j][now]=min(f[i][j][now],f[i][j][s]+f[i][j][now-s]-a[i][j])
这个是枚举子集,将两颗子树合并起来,要注意的一点就是根节点a[i][j]会重复计算依次,这也就是为什么最后还要减去a[i][j]。
至于枚举子集的方法就是:
for(int sub=(state-1)&state;sub;sub=(sub-1)&state)
另一个状态转移方程就是:
f[i][j][now]=min(f[i][j][now],f[i'][j'][now]+a[i][j])
这个就是往上下左右四个方向拓展,因为当一个状态的最优解确定,与它相邻的节点的最优解也就能确定下来。
1 #include<iostream> 2 #include<algorithm> 3 #include<cstring> 4 #include<cstdio> 5 #include<sstream> 6 #include<vector> 7 #include<stack> 8 #include<queue> 9 #include<cmath> 10 #include<map> 11 #include<set> 12 using namespace std; 13 typedef long long ll; 14 typedef pair<int,ll> pll; 15 const int INF = 0x3f3f3f3f; 16 const int maxn=100+5; 17 18 int n, m, k; 19 int a[15][15]; 20 int ans[15][15]; 21 int inq[20005]; 22 int f[15][15][2005]; 23 24 int dx[5]={1,-1,0,0}; 25 int dy[5]={0,0,1,-1}; 26 27 struct node 28 { 29 int i,j,state; 30 }path[15][15][2005]; 31 32 struct point 33 { 34 int i,j; 35 }; 36 37 queue<point> Q; 38 39 int c(point x) 40 { 41 return (x.i-1)*n+x.j; 42 } 43 44 void spfa(int state) 45 { 46 while (!Q.empty()) 47 { 48 point x=Q.front(); Q.pop(); inq[c(x)]=0; 49 for (int k=0;k<4;k++) 50 { 51 point y; 52 y.i=x.i+dx[k],y.j=x.j+dy[k]; 53 if (y.i<1||y.j<1||y.i>n||y.j>m) continue; 54 if (f[y.i][y.j][state]>f[x.i][x.j][state]+a[y.i][y.j]) 55 { 56 f[y.i][y.j][state]=f[x.i][x.j][state]+a[y.i][y.j]; 57 path[y.i][y.j][state].i=x.i,path[y.i][y.j][state].j=x.j,path[y.i][y.j][state].state=state; 58 if (!inq[c(y)]) {Q.push(y),inq[c(y)]=1;} 59 } 60 } 61 } 62 } 63 64 void dfs(int x, int y, int state) 65 { 66 if(!path[x][y][state].state) return; 67 ans[x][y]=1; 68 dfs(path[x][y][state].i,path[x][y][state].j,path[x][y][state].state); 69 if(path[x][y][state].i==x && path[x][y][state].j==y) 70 dfs(x,y,state^path[x][y][state].state); 71 } 72 73 int main() 74 { 75 //freopen("in.txt","r",stdin); 76 while(~scanf("%d%d",&n,&m)) 77 { 78 k=0; 79 memset(f,0x3f,sizeof(f)); 80 memset(ans,0,sizeof(ans)); 81 memset(path,0,sizeof(path)); 82 for(int i=1;i<=n;i++) 83 for(int j=1;j<=m;j++) 84 { 85 scanf("%d",&a[i][j]); 86 if(!a[i][j]) f[i][j][1<<(k++)]=0; 87 } 88 89 for(int state=1;state<(1<<k);state++) 90 { 91 for(int i=1;i<=n;i++) 92 for(int j=1;j<=m;j++) 93 { 94 for(int sub=(state-1)&state;sub;sub=(sub-1)&state) //枚举子集,将两个子树合并 95 { 96 if(f[i][j][state]>f[i][j][sub]+f[i][j][state-sub]-a[i][j]) //将两颗树合并,a[i][j]这个根顶点会多算一次,减掉 97 { 98 f[i][j][state]=f[i][j][sub]+f[i][j][state-sub]-a[i][j]; 99 path[i][j][state].i=i,path[i][j][state].j=j,path[i][j][state].state=sub; //路径记录 100 } 101 } 102 if(f[i][j][state]!=INF) 103 { 104 point p; 105 p.i=i, p.j=j; 106 Q.push(p); inq[c(p)]=1; 107 } 108 } 109 spfa(state); 110 } 111 112 int x,y; 113 for(int i=1;i<=n;i++) 114 for(int j=1;j<=m;j++) 115 if(!a[i][j]) {x=i;y=j;break;} 116 117 printf("%d\n",f[x][y][(1<<k)-1]); 118 119 dfs(x,y,(1<<k)-1); 120 for(int i=1;i<=n;i++) 121 { 122 for(int j=1;j<=m;j++) 123 { 124 if(!a[i][j]) printf("x"); 125 else if(ans[i][j]) printf("o"); 126 else printf("_"); 127 } 128 printf("\n"); 129 } 130 } 131 return 0; 132 }