【BZOJ2595】[WC2008] 游览计划(斯坦纳树入门)

点此看题面

大致题意: 给定一个(n imes m)的网格图,其中有(k)个必选点,而选择其他点都有一个代价。要求选出一个连通块,使得包含所有必选点,且总代价最小。

斯坦纳树

可见这篇博客:斯坦纳树入门小记

输出方案

这题作为斯坦纳树的板子,除一般的斯坦纳树以外,唯一要注意的就是方案的输出了。

我们只需在(DP)过程中记录下每个状态的前驱状态,然后从后往前(dfs)一遍即可。

注意根据点转移有两个前驱状态,但实际存储时只需记录一个就行了,具体实现详见代码。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 10
#define P(x,y) (((x)-1)*m+(y))
#define fi(x) (((x)-1)/m+1)
#define se(x) (((x)-1)%m+1)
#define INF f[0][0]
using namespace std;
const int dx[4]={0,0,1,-1},dy[4]={1,-1,0,0};
int n,m,k,a[N*N+5],f[N*N+5][1<<N],gx[N*N+5][1<<N],gs[N*N+5][1<<N];char ans[N+5][N+5];
int IQ[N*N+5];queue<int> q;I void SPFA(CI s)//SPFA优化转移
{
	RI i,k,x,y,nk,nx,ny;W(!q.empty())
	{
		for(IQ[k=q.front()]=0,q.pop(),x=fi(k),y=se(k),i=0;i^4;++i)
			(nx=x+dx[i])&&nx<=n&&(ny=y+dy[i])&&ny<=m&&(nk=P(nx,ny),f[nk][s]>f[k][s]+a[nk])&&
			(f[nk][s]=f[k][s]+a[nk],gx[nk][s]=k,gs[nk][s]=s,!IQ[nk]&&(q.push(nk),IQ[nk]=1));
	}
}
I void Draw(CI i,CI j)//从后往前DFS得到方案
{
	i&&(a[i]&&(ans[fi(i)][se(i)]='o'),gx[i][j]==i&&(Draw(i,j^gs[i][j]),0),Draw(gx[i][j],gs[i][j]),0);//注意根据点转移的两种状态
}
int main()
{
	RI i,j,rt;for(memset(f,63,sizeof(f)),scanf("%d%d",&n,&m),i=1;i<=n*m;++i)//读入数据
		scanf("%d",a+i),ans[fi(i)][se(i)]=a[i]?'_':'x',!a[i]&&(++k,f[rt=i][1<<k-1]=0);
	for(RI s=0,l=1<<k,x,y;s^l;++s)//枚举子集
	{
		for(i=1;i<=n*m;++i) for(j=s;j;j=(j-1)&s)//枚举作为根的点,根据点转移
			f[i][s]>f[i][j]+f[i][s^j]-a[i]&&(f[i][s]=f[i][j]+f[i][s^j]-a[i],gx[i][s]=i,gs[i][s]=j);
		for(i=1;i<=n*m;++i) f[i][s]^INF&&(q.push(i),IQ[i]=1);SPFA(s);//把有效点加入队列,根据边转移
	}
	for(printf("%d
",f[rt][(1<<k)-1]),Draw(rt,(1<<k)-1),i=1;i<=n;++i) puts(ans[i]+1);return 0;//输出答案
}
原文地址:https://www.cnblogs.com/chenxiaoran666/p/BZOJ2595.html