【bzoj2595】[Wc2008]游览计划 斯坦纳树
题目描述
给出一个N×M的方格图,每个格子有自己权值,权值为0的格子已被选定。现要再选定一些格子,使得所有选定的格子(包括一开始已被选定的格子)四联通,并且选定的格子的权值之和最小。输出这个最小权值及一种可行方案。
输入
第一行有两个整数,N和 M,描述方块的数目。
接下来 N行, 每行有 M 个非负整数, 如果该整数为 0, 则该方块为一个景点;
否则表示控制该方块至少需要的志愿者数目。 相邻的整数用 (若干个) 空格隔开,
行首行末也可能有多余的空格。
输出
由 N + 1行组成。第一行为一个整数,表示你所给出的方案
中安排的志愿者总数目。
接下来 N行,每行M 个字符,描述方案中相应方块的情况:
z ‘_’(下划线)表示该方块没有安排志愿者;
z ‘o’(小写英文字母o)表示该方块安排了志愿者;
z ‘x’(小写英文字母x)表示该方块是一个景点;
注:请注意输出格式要求,如果缺少某一行或者某一行的字符数目和要求不
一致(任何一行中,多余的空格都不允许出现) ,都可能导致该测试点不得分。
样例输入
4 4
0 1 1 0
2 5 5 1
1 5 5 1
0 1 1 0
样例输出
6
xoox
___o
___o
xoox
题解
斯坦纳树裸题
但是本题的斯坦纳树是点权,与大多数图的边权不同,下边讲解一般的边权做法,然后再将本题的题解。
斯坦纳树:给出一些点,选出若干条边使得这些点连通,求总边权的最值。
斯坦纳树是NP问题,不存在多项式时间内的解法,求解方法是状压dp。
设 $f[i][j]$ 表示选择若干条边,使得状态为 $i$ 的给定点连通,并且当前可以选择下一条边的端点为 $j$ 的最小边权和。初始状态 $f[2^k][pos[k]]=0$ ,其中 $pos[k]$ 为第 $k$ 个给定点的编号。
那么我们对于每个 $i$ 和 $j$ ,首先枚举 $i$ 的子集 $k$ ,用 $f[k][j]+f[i-k][j]$ 更新 $f[i][j]$ 。
然后再考虑同层转移:如果 $x$ 与 $y$ 边权为 $z$ ,用 $f[i][x]+z$ 更新 $f[i][y]$ ,用 $f[i][y]$ 更新 $f[i][x]$ 。容易发现这个转移就是最短路,因此使用堆优化Dijkstra跑一遍得出所有的 $f[i][j]$ 。
最终答案就是 $min\{f[2^p-1][i]\}$
这个dp的理解比较显然,时间复杂度 $O(3^p·n+2^p·m\log n)$ ,其中 $p$ 是给定点的数目。
那么对于本题,由于权值在点上,因此枚举子集转移时,当前点会重复选择,需要减去代价。
在同层转移时,求的就是点权最短路,边长为目标点的点权。
设 $f[i][j][k]$ 表示选定若干条边,使得状态为 $i$ 的给定点连通,并且当前可以选择下一条边的相邻点为 $(j,k)$ 的最小点权和。初始状态 $f[2^k][posx[k]][posy[k]]=0$ .
首先枚举子集转移:$f[i][j][k]=min_{l\subseteq i}f[l][j][k]+f[i-l][j][k]-a[j][k]$ ,然后同层转移,与 $(j,k)$ 相邻的点的 $f$ 加上 $a[j][k]$ 可以转移到 $f[i][j][k]$ 。
输出方案的话直接记录路径,记录从哪种方式的哪个状态转移过来,dfs一遍即可知道选定的点。
时间复杂度 $O(3^p·nm+2^p·nm\log n)$
#include <queue> #include <cstdio> #include <cstring> #include <algorithm> #define N [1030][11][11] using namespace std; typedef pair<int , int> pr; typedef pair<int , pr> ppr; priority_queue<ppr> q; int a[11][11] , px[11] , py[11] , f N , vis N , opt N , lx N , ly N , flag[11][11]; void dfs(int i , int x , int y) { if(!f[i][x][y]) return; flag[x][y] = 1; if(opt[i][x][y]) dfs(i , lx[i][x][y] , ly[i][x][y]); else dfs(lx[i][x][y] , x , y) , dfs(ly[i][x][y] , x , y); } int main() { int n , m , p = 0 , i , j , k , l , x , y , ans = 1 << 30; scanf("%d%d" , &n , &m); for(i = 1 ; i <= n ; i ++ ) { for(j = 1 ; j <= m ; j ++ ) { scanf("%d" , &a[i][j]); if(!a[i][j]) px[p] = i , py[p++] = j; } } memset(f , 0x3f , sizeof(f)); for(i = 0 ; i < p ; i ++ ) f[1 << i][px[i]][py[i]] = 0; for(i = 1 ; i < (1 << p) ; i ++ ) { for(j = 1 ; j <= n ; j ++ ) for(k = 1 ; k <= m ; k ++ ) for(l = i ; l ; l = i & (l - 1)) if(f[i][j][k] > f[l][j][k] + f[i ^ l][j][k] - a[j][k]) f[i][j][k] = f[l][j][k] + f[i ^ l][j][k] - a[j][k] , opt[i][j][k] = 0 , lx[i][j][k] = l , ly[i][j][k] = i ^ l; for(j = 1 ; j <= n ; j ++ ) for(k = 1 ; k <= m ; k ++ ) q.push(ppr(-f[i][j][k] , pr(j , k))); while(!q.empty()) { x = q.top().second.first , y = q.top().second.second , q.pop(); if(vis[i][x][y]) continue; vis[i][x][y] = 1; if(x > 1 && f[i][x - 1][y] > f[i][x][y] + a[x - 1][y]) f[i][x - 1][y] = f[i][x][y] + a[x - 1][y] , opt[i][x - 1][y] = 1 , lx[i][x - 1][y] = x , ly[i][x - 1][y] = y , q.push(ppr(-f[i][x - 1][y] , pr(x - 1 , y))); if(x < n && f[i][x + 1][y] > f[i][x][y] + a[x + 1][y]) f[i][x + 1][y] = f[i][x][y] + a[x + 1][y] , opt[i][x + 1][y] = 1 , lx[i][x + 1][y] = x , ly[i][x + 1][y] = y , q.push(ppr(-f[i][x + 1][y] , pr(x + 1 , y))); if(y > 1 && f[i][x][y - 1] > f[i][x][y] + a[x][y - 1]) f[i][x][y - 1] = f[i][x][y] + a[x][y - 1] , opt[i][x][y - 1] = 1 , lx[i][x][y - 1] = x , ly[i][x][y - 1] = y , q.push(ppr(-f[i][x][y - 1] , pr(x , y - 1))); if(y < m && f[i][x][y + 1] > f[i][x][y] + a[x][y + 1]) f[i][x][y + 1] = f[i][x][y] + a[x][y + 1] , opt[i][x][y + 1] = 1 , lx[i][x][y + 1] = x , ly[i][x][y + 1] = y , q.push(ppr(-f[i][x][y + 1] , pr(x , y + 1))); } } for(i = 0 ; i < p ; i ++ ) if(ans > f[(1 << p) - 1][px[i]][py[i]]) ans = f[(1 << p) - 1][px[i]][py[i]] , x = i; printf("%d\n" , ans); dfs((1 << p) - 1 , px[x] , py[x]); for(i = 1 ; i <= n ; i ++ ) { for(j = 1 ; j <= m ; j ++ ) { if(!a[i][j]) printf("x"); else if(flag[i][j]) printf("o"); else printf("_"); } puts(""); } return 0; }