P3070 [USACO13JAN]Island Travels G 题解

题目传送门

博客园传送门

一道耗费了本蒟蒻与某机房卷王半天的恶心题

题目描述

给定一个图,求每个 X 连通块之间的最短 Hamilton 路径。假如您不知道 Hamilton 路径是什么

分析

这题本质上是 Hamilton 路径的模板题,但是难就难在题目并没有直接将一个抽象过的无向图给你,而是给了一个具体的地图。那么我们第一步就应该将这个地图抽象成一个无向图。

First

首先我们来找连通块。记 B e l o n g x , y Belong_{x,y} Belongx,y 为坐标为 ( x , y ) (x,y) (x,y) 的 ‘X’ 所属的连通块, t o t tot tot 为连通块的总数, m p x , y mp_{x,y} mpx,y 为我们存的地图(将 ‘S’ 存为 1 1 1,‘X’ 存为 0 0 0,‘.’ 存为 - 1 1 1 )。则我们可以用 DFS 来求连通块 (我是不会说我不会 BFS 的)

ll dx[4]={0,1,0,-1};
ll dy[4]={1,0,-1,0};
ll belong[ma][ma],tot=0;
ll mp[ma][ma];

void dfs1(ll x,ll y){
	if(x>n||y>m||x<1||y<1) return;//到边界停止
	if(mp[x][y]!=0||belong[x][y]) return;//找过了或不是陆地就不找了
	if(mp[x][y]==0){
		belong[x][y]=tot;//记录连通块
	}
	for(ll i=0;i<4;i++){
		ll xx=x+dx[i],yy=y+dy[i];//位移深搜
		dfs1(xx,yy);
	}
}

Second

找完了联通块,就开始建图了。我们记 w i , j w_{i,j} wi,j 为联通块 i i i j j j 的最短路径。那么我们要如何求这个最短路径呢?类比双端队列优化的 SPFA,我们用一个双端队列对每一个连通块都跑一遍 BFS,记 d i s i , j dis_{i,j} disi,j 为当前连通块到 ( i , j ) (i,j) (i,j) 的最短路径,再去用 d i s i , j dis_{i,j} disi,j 的最小值更新 w i , j w_{i,j} wi,j 即可。

deque<pair<ll,ll>> q;
ll dis[ma][ma];
ll w[16][16];

void bfs(){
	while(q.size()){
		ll x=q.front().first,y=q.front().second;
		q.pop_front();
		for(ll i=0;i<4;i++){
			ll xx=x+dx[i],yy=y+dy[i];
			if(xx>n||yy>m||xx<1||yy<1) continue;//越界
			if(dis[xx][yy]||mp[xx][yy]==-1) continue;//访问过
			//如果是 'X' ,则推到队首,距离不变
			if(mp[xx][yy]==0){
				dis[xx][yy]=dis[x][y];
				q.push_front(make_pair(xx,yy));
			}
			//如果是 'S' ,则推到队尾,距离加1
			else if(mp[xx][yy]==1){
				dis[xx][yy]=dis[x][y]+1;
				q.push_back(make_pair(xx,yy));
			}
		}
	}
}

//in main()

memset(w,0x3f,sizeof(w));
for(ll i=1;i<=tot;i++){
	memset(dis,0,sizeof(dis));
	for(ll j=1;j<=n;j++){
		for(ll k=1;k<=m;k++){
			if(belong[j][k]==i){
				//如果(j,k)属于第i个连通块,则推到队首
				q.push_front(make_pair(j,k));
				dis[j][k]=1;//小技巧:为了防止0在 BFS 时一直不被更新,初始便设置为1。
			}
		}
	}
	bfs();
	for(ll j=1;j<=n;j++){
		for(ll k=1;k<=m;k++){
			//(j,k)为枚举的点
			for(ll cx=1;cx<=n;cx++){
				for(ll cy=1;cy<=m;cy++){
					//(cx,cy)为属于第i个连通块的点
					if(belong[j][k]&&belong[cx][cy]&&belong[cx][cy]==i){
						w[i][belong[j][k]]=min(w[i][belong[j][k]],dis[j][k]-dis[cx][cy]);//更新w
					}
				}
			}
		}
	}
}

Final

现在,我们总算将这题最最 恶心 难的地方处理完啦,接下来便是纯纯的 最短 Hamilton 路径 板子。但是注意我们是可以随便选起点的,所以要对每一个点作为起点都跑一遍状压 DP,求出最小的答案即可。

状压 DP 具体见这题

ll dp[16][1<<16];

memset(dp,0x3f,sizeof(dp));
for(ll i=1;i<=tot;i++) dp[i][1<<(i-1)]=0;
for(ll i=1;i<=tot;i++){
	for(ll j=1<<(i-1)+1;j<1<<tot;j++){
		for(ll k=1;k<=tot;k++){
			if((j>>(k-1))&1){
				for(ll l=1;l<=tot;l++){
					if((j^1<<(k-1))>>(l-1)&1){
						dp[k][j]=min(dp[k][j],dp[l][j^1<<(k-1)]+w[l][k]);
					}
				}
			}
		}
	}
}
ll ans=inf;
for(ll i=1;i<=tot;i++) ans=min(ans,dp[i][(1<<tot)-1]);

完整代码

#include<bits/stdc++.h>
#define ll long long
#define ma 100
#define mod 1000000007
#define il inline
using namespace std;
const ll inf=1e18+10;
ll read(){
	char ch=getchar();
	ll x=0,f=1;
	while(ch>'9'||ch<'0'){
	if(ch=='-') f=-1;
	ch=getchar();
	}
	while('0'<=ch&&ch<='9'){
	x=x*10+ch-'0';
	ch=getchar();
	}
	return x*f;
}
ll n,m;
ll mp[ma][ma];
ll dx[4]={0,1,0,-1};
ll dy[4]={1,0,-1,0};
ll dp[16][1<<16];
ll w[16][16];
ll belong[ma][ma],tot=0;
deque<pair<ll,ll>> q;
ll dis[ma][ma];
void dfs1(ll x,ll y){
	if(x>n||y>m||x<1||y<1) return;
	if(mp[x][y]!=0||belong[x][y]) return;
	if(mp[x][y]==0){
		belong[x][y]=tot;
	}
	for(ll i=0;i<4;i++){
		ll xx=x+dx[i],yy=y+dy[i];
		dfs1(xx,yy);
	}
}
void bfs(){
	while(q.size()){
		ll x=q.front().first,y=q.front().second;
		q.pop_front();
		for(ll i=0;i<4;i++){
			ll xx=x+dx[i],yy=y+dy[i];
			if(xx>n||yy>m||xx<1||yy<1) continue;
			if(dis[xx][yy]||mp[xx][yy]==-1) continue;
			if(mp[xx][yy]==0){
				dis[xx][yy]=dis[x][y];
				q.push_front(make_pair(xx,yy));
			}
			else if(mp[xx][yy]==1){
				dis[xx][yy]=dis[x][y]+1;
				q.push_back(make_pair(xx,yy));
			}
		}
	}
}
int main(){
	n=read(),m=read();
	for(ll i=1;i<=n;i++){
		char x[100];
		cin>>x+1;
		for(ll j=1;j<=m;j++){
			if(x[j]=='.') mp[i][j]=-1;
			if(x[j]=='X') mp[i][j]=0;
			if(x[j]=='S') mp[i][j]=1;
		}
	}
	for(ll i=1;i<=n;i++){
		for(ll j=1;j<=m;j++){
			if(!belong[i][j]&&!mp[i][j]){
				++tot;
				dfs1(i,j);
			}
		}
	}
	memset(w,0x3f,sizeof(w));
	for(ll i=1;i<=tot;i++){
		memset(dis,0,sizeof(dis));
		for(ll j=1;j<=n;j++){
			for(ll k=1;k<=m;k++){
				if(belong[j][k]==i){
					q.push_front(make_pair(j,k));
					dis[j][k]=1;
				}
			}
		}
		bfs();
		for(ll j=1;j<=n;j++){
			for(ll k=1;k<=m;k++){
				for(ll cx=1;cx<=n;cx++){
					for(ll cy=1;cy<=m;cy++){
						if(belong[j][k]&&belong[cx][cy]&&belong[cx][cy]==i){
							w[i][belong[j][k]]=min(w[i][belong[j][k]],dis[j][k]-dis[cx][cy]);
						}
					}
				}
			}
		}
	}
	memset(dp,0x3f,sizeof(dp));
	for(ll i=1;i<=tot;i++) dp[i][1<<(i-1)]=0;
	for(ll i=1;i<=tot;i++){
		for(ll j=1<<(i-1)+1;j<1<<tot;j++){
			for(ll k=1;k<=tot;k++){
				if((j>>(k-1))&1){
					for(ll l=1;l<=tot;l++){
						if((j^1<<(k-1))>>(l-1)&1){
							dp[k][j]=min(dp[k][j],dp[l][j^1<<(k-1)]+w[l][k]);
						}
					}
				}
			}
		}
	}
	ll ans=inf;
	for(ll i=1;i<=tot;i++) ans=min(ans,dp[i][(1<<tot)-1]);
	printf("%lld\n",ans);
	return 0;
}

部分思想参考了卷王cry

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值