1. 程式人生 > 實用技巧 >【BZOJ2595】[WC2008] 遊覽計劃(斯坦納樹入門)

【BZOJ2595】[WC2008] 遊覽計劃(斯坦納樹入門)

點此看題面

大致題意: 給定一個\(n\times 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\n",f[rt][(1<<k)-1]),Draw(rt,(1<<k)-1),i=1;i<=n;++i) puts(ans[i]+1);return 0;//輸出答案
}