LG 4294[WC2008]游览计划(最小斯坦纳树)

LG 4294[WC2008]游览计划

前言

我就喜欢这种重题的题,不知道是WC搬的CF还是CF搬的WC,不管了,反正一样就是了。

既可以水 \(2\) 篇题解,也可以搞掉一黑一紫,何乐而不为?

解题思路

\(k\) 很小,又是连通性问题,显然最小斯坦纳树,问题是如何建图?如何求出方案?

建图

我们可以按四联通来建图,不用管边权。由于在这张图里只有点权,我们的斯坦纳树 DP 转移方程会有所改变(不要重复计算点权),具体看代码,其实很好理解(只要你理解了斯坦纳树的板子)。

求出方案

这个有点难度。其实可以和大部分 DP 一样,需要记录一下转移决策点。SPFA 可以这样,三角形不等式的部分怎么办?

我们其实可以这样,如果可以已通过 SPFA 记录的决策点一直往前推,就往前,直到结束为止,我们就在把当前状态的递推在跑一遍即可。

//Don't act like a loser.
//This code is written by huayucaiji
//You can only use the code for studying or finding mistakes
//Or,you'll be punished by Sakyamuni!!!
#include<bits/stdc++.h>
#define int long long
using namespace std;

int read() {
	char ch=getchar();
	int f=1,x=0;
	while(ch<'0'||ch>'9') {
		if(ch=='-')
			f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9') {
		x=x*10+ch-'0';
		ch=getchar();
	}
	return f*x;
}

const int MAXN=110,MAXK=11,INF=2e9;
const int dx[]={1,0,-1,0};
const int dy[]={0,1,0,-1};

int n,m,k,cnt;
int a[MAXN],h[MAXN],f[MAXN][1<<MAXK],g[MAXN][1<<MAXK];
char ans[MAXN];
bool inq[MAXN],vis[MAXN][1<<MAXK];
queue<int> q;

int idx(int i,int j) {
	return (i-1)*m+j;
} 
bool ok(int i,int j) {
	if(i>n||i<1||j>m||j<1) {
		return 0;
	}
	return 1;
}

struct edge {
	int to,nxt;
}e[10000];

void addedge(int u,int v) {
	e[++cnt].nxt=h[u];
	e[cnt].to=v;
	h[u]=cnt;
}

void SPFA(int s) {
	while(!q.empty()) {
		int u=q.front();
		q.pop();
		inq[u]=0;
		
		for(int i=h[u];i;i=e[i].nxt) {
			int v=e[i].to;
			if(f[v][s]>f[u][s]+a[v]) {
				f[v][s]=f[u][s]+a[v];
				g[v][s]=u;
				if(!inq[v]) {
					q.push(v);
					inq[v]=1;
				}
			}
		}
	}
}

void getans(int u,int s) {
	if(vis[u][s]) {
		return ;
	}
	vis[u][s]=1;
	
	ans[u]='o';
    //一直往前推
	while(g[u][s]!=-1&&f[g[u][s]][s]+a[u]==f[u][s]) {
		u=g[u][s];
		getans(u,s);
	}
	for(int t=s&(s-1);t;t=(t-1)&s) {
		if(f[u][t]+f[u][s^t]-a[u]==f[u][s]) {
			getans(u,t);
			getans(u,s^t);
			break;
            //重新递推
		}
	}
}

signed main() {
	cin>>n>>m;
	for(int i=1;i<=n*m;i++) {
		for(int j=0;j<=1023;j++) {
			f[i][j]=INF;
			g[i][j]=-1;
		}
	}
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=m;j++) {
			ans[idx(i,j)]='_';
			cin>>a[idx(i,j)];
			if(!a[idx(i,j)]) {
				f[idx(i,j)][1<<k]=0;
				k++;
			}
			for(int p=0;p<4;p++) {
				if(ok(i+dx[p],j+dy[p])) {
					addedge(idx(i,j),idx(i+dx[p],j+dy[p]));
				}
			}
		}
	}
	
	int mx=(1<<k)-1;
	
	for(int s=0;s<=mx;s++) {
		for(int i=1;i<=n*m;i++) {
			for(int t=s&(s-1);t;t=(t-1)&s) {
				f[i][s]=min(f[i][s],f[i][t]+f[i][s^t]-a[i]);
                //同一点的点权不要重复算
			}
			if(f[i][s]<INF) {
				inq[i]=1;
				q.push(i);
			}
		}
		SPFA(s);
	}
	
	int minn=INF,mark=0;
	for(int i=1;i<=n*m;i++) {
		if(f[i][mx]<minn) {
			minn=f[i][mx];
			mark=i;
		}
	}
	
	cout<<minn<<endl;
	getans(mark,mx);
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=m;j++) {
			if(!a[idx(i,j)]) {
				ans[idx(i,j)]='x';
			}
			printf("%c",ans[idx(i,j)]);
		}
		puts("");
	}
	return 0;
}

posted @ 2021-06-28 21:25  huayucaiji  阅读(32)  评论(0编辑  收藏  举报