• 【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·mlog n)$ ,其中 $p$ 是给定点的数目。

    那么对于本题,由于权值在点上,因此枚举子集转移时,当前点会重复选择,需要减去代价。

    在同层转移时,求的就是点权最短路,边长为目标点的点权。

    设 $f[i][j][k]$ 表示选定若干条边,使得状态为 $i$ 的给定点连通,并且当前可以选择下一条边的相邻点为 $(j,k)$ 的最小点权和。初始状态 $f[2^k][posx[k]][posy[k]]=0$ .

    首先枚举子集转移:$f[i][j][k]=min_{lsubseteq 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·nmlog 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
    " , 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;
    }
    
  • 相关阅读:
    【学习】Linux Shell脚本编程
    Linux 系统进程相关命令
    Linux 文件权限管理
    Linux 用户关联命令
    Linux shell 及命令汇总
    服务器通过树莓派控制继电器
    华为绩效管理:这样减员、增效、加薪,不服不行!!
    工作十年的程序员,却拿着毕业三年的工资,再不开窍就真晚了!
    员工离职原因,只有两点最真实,其他都是扯淡!
    最全面的2017物联网安全事件盘点
  • 原文地址:https://www.cnblogs.com/GXZlegend/p/8075585.html
Copyright © 2020-2023  润新知