BZOJ 2595: [Wc2008]游览计划

斯坦纳树模板题,记录一下路径即可
//斯坦纳树,解决这一类问题:给定一张图,并给出k个点,问利用边与其它点将这k个点连接起来的最小边权和为多少,k一般为10
//一眼看状压dp,然后就想到了洛谷1171售货员的难题 好像与它差不多,看了下后天差地别,完全不一样 ,因为这题没有起点 
// 开始想状态,想不出,看了很久的题解后,感觉还是懵懵的
//转移过程有点树形dp的味道,有根、子树这样的概念,但是子树这个概念在这里不太一样 
#include <bits/stdc++.h>
using namespace std;
const int dx[4]={0,-1,0,1},dy[4]={-1,0,1,0};
const int N=15;
int n,m,tot,max_statue,l,r,ex,ey;
int c[N][N],ans[N][N],f[N][N][1<<10];//定义f[i][j][p]:根为[i][j],已经联通的点的集合为p的最小花费
//什么叫做[i][j]为根呢?就是说当前走到的点为[i][j]。
//可知,最后一个走到的点,一定是某个景点,即最后的答案一定是 f[i][j][max_statue],c[i][j]=0 
bool vis[N][N];
struct node{int x,y;}q[N*N];
struct number{int x,y,p;}pre[N][N][1<<10];

void spfa(int p)
{
	while (l<=r)
	{
		int x=q[l].x,y=q[l].y;
		l++;
		vis[x][y]=false;
		for (register int i=0; i<4; ++i)
		{
			int xx=x+dx[i],yy=y+dy[i];
			if (xx<1 || xx>n || yy<1 || yy>m) continue;
			if (f[xx][yy][p]>f[x][y][p]+c[xx][yy])
			{
				f[xx][yy][p]=f[x][y][p]+c[xx][yy];
				pre[xx][yy][p]=(number){x,y,p};
				if (!vis[xx][yy]) r++,q[r]=(node){xx,yy},vis[xx][yy]=true;
			}
		}
	}
}

void dfs(int x,int y,int p)
{
	//搜索路径 
	ans[x][y]=1;
	if (!pre[x][y][p].p) return;
	number now=pre[x][y][p];
	if (now.x==x && now.y==y)
	{
		dfs(now.x,now.y,now.p);
		dfs(now.x,now.y,p^now.p);
		//可以把[now.x][now.y][now.p],[now.x][now.y][now.p]看作[x][y][p]的子树来理解 
	}
	else
	{
		dfs(now.x,now.y,now.p);
	}
}

int main(){
	scanf("%d%d",&n,&m);
	memset(f,60,sizeof(f));
	for (register int i=1; i<=n; ++i)
	for (register int j=1; j<=m; ++j)
	{
		scanf("%d",&c[i][j]);
		if (!c[i][j]) f[i][j][1<<tot]=0,tot++,ex=i,ey=j;
		//把景点拎出来 
	}
	
	//转移方程有两个:
	//1.f[i][j][p]=min(f[i][j][pp]+f[i][j][p^pp]-c[i][j])  (pp为p子集)  
	//走到i,j,联通的点为p,可以由走到i,j,联通的点为pp 和 走到i,j,联通的点为p^pp得到,要减去c[i][j],因为算了2次 
	//2.f[i][j][p]=min(f[ii][jj][p]+c[i][j])   [ii][jj]与[i][j]相邻
	//走到i,j,联通的点为p,可以由一个和它相邻的点,联通的点为p得到,要加上走到[i][j]的花费c[i][j] 
	max_statue=(1<<tot)-1;
	for (register int p=1; p<=max_statue; ++p) 
	{
		l=1; r=0;
		for (register int i=1; i<=n; ++i)
		for (register int j=1; j<=m; ++j)
		{
			for (register int pp=p; pp; pp=p&(pp-1)) //枚举子集 
			{
				if (f[i][j][p]>f[i][j][pp]+f[i][j][p^pp]-c[i][j]) 
				{
					f[i][j][p]=f[i][j][pp]+f[i][j][p^pp]-c[i][j],pre[i][j][p]=(number){i,j,pp}; 
				}
			}
			r++,q[r]=(node){i,j},vis[i][j]=true;	
		}
		spfa(p);    //对于有图的地方,用spfa进行转移 
	}
	
	dfs(ex,ey,max_statue);  //确定路径 
	printf("%d\n",f[ex][ey][max_statue]);
	for (register int i=1; i<=n; ++i)
	{
		for (register int j=1; j<=m; ++j) 
		if (!c[i][j]) putchar('x'); else if (ans[i][j]) putchar('o'); else putchar('_');
		putchar('\n');
	}
return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值