码迷,mamicode.com
首页 > 其他好文 > 详细

【bzoj2595】[Wc2008]游览计划 斯坦纳树

时间:2017-12-20 21:51:26      阅读:185      评论:0      收藏:0      [点我收藏+]

标签:测试   names   cond   div   define   std   while   push   输入   

题目描述

给出一个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;
}

 

【bzoj2595】[Wc2008]游览计划 斯坦纳树

标签:测试   names   cond   div   define   std   while   push   输入   

原文地址:http://www.cnblogs.com/GXZlegend/p/8075585.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!