1. 程式人生 > >【BZOJ】2595: [Wc2008]遊覽計劃-斯坦納樹&插頭DP

【BZOJ】2595: [Wc2008]遊覽計劃-斯坦納樹&插頭DP

傳送門:bzoj2595


題解

法1:斯坦納樹

g [ i ] g[i] 表示點 i

i 的價值。

狀壓DP,設 f [ i ] [ m a s k

] f[i][mask] 表示以 i i 點為根的連通覆蓋的景點集合為 m a s
k mask
時的最小花費。

列舉 m a s k mask 的子集 s s f [ i ] [ m a s k ] = m i n ( f [ i ] [ s ] + f [ i ] [ m a s k s ] g [ i ] ) f[i][mask]=min(f[i][s]+f[i][mask-s]-g[i])
同時 f [ i ] [ m a s k ] = m i n ( f [ j ] [ m a s k ] + g [ i ] ) f[i][mask]=min(f[j][mask]+g[i]) ,這個可以 s p f a spfa 一起求。

#include<bits/stdc++.h>
using namespace std;
const int N=11,M=(1<<10)+10,inf=0x3f3f3f3f;

int n,m,q,a[N][N],f[N][N][M];
int s,S;
bool inq[N][N],vs[N][N];

struct Q{int x,y,z;}pre[N][N][M];
struct P{int x,y;};
queue<P>que;

int dx[5]={-1,1,0,0};
int dy[5]={0,0,-1,1};

inline void spfa()
{
	int i,v,ix,iy,x,y;
	for(;que.size();){
		ix=que.front().x;iy=que.front().y;que.pop();
	    v=f[ix][iy][s];
		for(i=0;i<4;++i){
			x=ix+dx[i];y=iy+dy[i];
	        if(x<1 || x>n || y<1 || y>m || f[x][y][s]<=v+a[x][y]) continue;
	        f[x][y][s]=v+a[x][y];pre[x][y][s]=(Q){ix,iy,s};
	        if(inq[x][y]) continue;inq[x][y]=true;que.push((P){x,y});
		}
		inq[ix][iy]=false;
	}
}

void dfs(int x,int y,int z)
{
	vs[x][y]=true;
	Q tp=pre[x][y][z];if(!tp.x) return;
	dfs(tp.x,tp.y,tp.z);
	if((tp.x==x)&&(tp.y==y)) dfs(x,y,z-tp.z);
}

int main(){
    int i,j,t,res,mx,my;
    memset(f,0x3f,sizeof(f));
	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]) {f[i][j][1<<q]=0;q++;mx=i;my=j;}
     }
     if(q<2){putchar('0');return 0;}
     S=(1<<q)-1;
     for(s=1;s<=S;++s){
       for(i=1;i<=n;++i)
     	 for(j=1;j<=m;++j){
     	 	for(t=s&(s-1);t;t=s&(t-1)){
     	   	  res=f[i][j][t]-a[i][j]+f[i][j][s-t];
     	   	  if(res>=f[i][j][s]) continue;
     	   	  f[i][j][s]=res;pre[i][j][s]=(Q){i,j,t};
       	    }
     	    if(f[i][j][s]<inf) que.push((P){i,j}),inq[i][j]=true;
     	 }
		spfa();
     }
     printf("%d\n",f[mx][my][S]);
	 dfs(mx,my,S);
     for(i=1;i<=n;++i){
     	for(j=1;j<=m;++j){
     		if(!a[i][j]) putchar('x');
     		else if(vs[i][j]) putchar('o');
     		else putchar('_');
     	}
     	puts("");
     }
     return 0;
}

法2:插頭DP

想了很久,發現括號匹配和獨立插頭很不好表示。因為要求的是聯通塊,轉移時單雙插頭可以任意切換,所以用最小表示法最適合。

如何限制來滿足題意呢?

景點必須有插頭。
對於不是景點的位置,若它上方有插頭且上方的聯通塊沒有其它插頭可以延伸時,它必須要選,否則可以不選。

當已經處理完所有景點位置後,每個聯通塊不超過1的狀態可用於更新答案。

有點難打,碼了好久。

#include<bits/stdc++.h>
using namespace std;
const int mod=23333,N=11,M=1e6+10,inf=0x3f3f3f3f;

int n,m,q,g[N][N],pr,pt=1,mx,my;
int pbs,bs,ans=inf,lst,pre[M],vs[10],vp[10];
bool fw[N][N];

struct P{int x,y,z;}ifo[M];

struct Hs{
	int val[mod],key[mod],sz,hs[mod];
	inline void itia(){
		memset(val,0x3f,sizeof(val));memset(hs,0,sizeof(hs));
		memset(key,0xff,sizeof(key));sz=0;
	}
	inline void nh(int u,int v){hs[u]=++sz;key[sz]=v;}
	inline int pls(int S){
		for(int i=S%mod;;i=(i+1==mod)?0:(i+1)){
			if(!hs[i]) nh(i,S);
			if(key[hs[i]]==S) return hs[i];
		}
	}
}f[2];

inline int gt(int S,int pos){return (!pos)?0:((S>>((pos-1)*3))&7);}
inline void sett(int &S,int pos,int v)
{pos=(pos-1)*3;S|=(7<<pos);S^=(7<<pos);S|=(v<<pos);}
inline int fd(int S,int x)
{
	int i,re=0;
	for(i=1;i<=m;++i,S>>=3) re+=((S&7)==x);
	return re;
}
inline int trs(int S)
{
	memset(vs,0xff,sizeof(vs));
	int i,re=0,cot=0;vs[0]=0;
	for(i=1;i<=m;++i,S>>=3) vp[i]=(S&7);
	for(i=m;i;--i){
		if(vs[vp[i]]==-1) vs[vp[i]]=++cot;
	    re=(re<<3)|(vs[vp[i]]);
	}
	return re;
}

inline void calans()
{
	int i,S,vl;
	for(int k=f[pr].sz;k;--k){
		S=f[pr].key[k];vl=f[pr].val[k];
		if(ans<=vl) continue;
		for(i=1;i<=m;++i,S>>=3) if((S&7)>1) break;
		if(i<=m) continue;
		ans=vl;lst=bs+k;
	}
}

inline void cal(int x,int y)
{
    int i,k,S,vl,a,b,id,res;
	pr^=1;pt^=1;f[pr].itia();pbs=bs;bs+=f[pt].sz;
	for(k=f[pt].sz;k;--k){
		S=f[pt].key[k];vl=f[pt].val[k];
		a=gt(S,y-1);b=gt(S,y);
		if(((g[x][y]!=0)&&((!b)||(fd(S,b)>1)))){
			res=S;sett(res,y,0);id=f[pr].pls(trs(res));
			if(vl<f[pr].val[id]){
			  ifo[bs+id]=(P){x,y,0};
			  f[pr].val[id]=vl;pre[bs+id]=pbs+k;
			}
		}
		if((!a)&&(!b)) sett(S,y,7);
		else if(a&&(!b)) sett(S,y,a);
		else if(a&&b&&(a!=b)){
			for(res=S,i=1;i<=m;++i,res>>=3)
				if((res&7)==a) sett(S,i,b);
		}
		id=f[pr].pls(trs(S));
		if(vl+g[x][y]>=f[pr].val[id]) continue;ifo[bs+id]=(P){x,y,1};
		f[pr].val[id]=vl+g[x][y];pre[bs+id]=pbs+k;
	}
}

int main(){
	int i,j;
	scanf("%d%d",&n,&m);
    for(i=1;i<=n;++i)
     for(j=1;j<=m;++j){
     	scanf("%d",&g[i][j]);
     	if(!g[i][j]) mx=i,my=j,q++;
     }
     if(q<2){putchar('0');return 0;}
     f[0].itia();i=f[0].pls(0);f[0].val[i]=0;
     for(i=1;i<=n;++i){
     	for(j=1;j<=m;++j){
     		cal(i,j);
     		if((i>mx)||(i==mx&&j>=my)) calans();
     	}
     }
     printf("%d\n",ans);
     for(i=lst;i>1;i=pre[i]) if(ifo[i].z) fw[ifo[i].x][ifo[i].y]=1;
     for(i=1;i<=n;++i){
     	for(j=1;j<=m;++j){
     		if(!g[i][j]) putchar('x');
     		else if(fw[i][j]) putchar('o');
     		else putchar('_');
     	}
     	puts("");
     }
     return 0;
}