洛谷P1979 华容道 题解

嘤嘤嘤泥古管理至今没给申题解
QAQ只有五天啦..

洛谷P1979 华容道 题解

距离NOIP2018还有7天qwq写篇题解积攒下人品
(第一次写紫题题解还有些小激动)

一些说明

1、本题解与代码中的0/1/2/3分别代表上/下/左/右

2、为了清晰表述,题解中记目标棋子(题目中的 (SX,SY) )为 (B) ,目标格子(题目中的 (TX,TY) 为 )(C),空白格为(E)

【思路分析】
如几位dalao所说,这道题的建图就是把可行的状态连边,对于每次询问跑最短路即可。

我用 $ ok[i][j][0/1/2/3] $表示 (B)位于点((i,j))时,四个方向是否可能有空白格(先不考虑每组询问中空白格是否可以到达,只考虑该格子是否为固定格,或越界。)

此部分代码如下:

//判断哪些状态是合法的 
	for(int i=1;i<=n;i++)//空白格在棋子上下左右 
	for(int j=1;j<=m;j++){
		if(!mapp[i][j])continue;//不可能有此状态 
		for(int k=0;k<4;k++)//四个方向 
		if(judge(i+xx[k],j+yy[k]))ok[i][j][k]=1;
		}

judge函数用来判断是否为空白格或越界:

bool judge(int ax,int ay){
	if(ax<=0||ax>n||ay<=0||ay>m)return 0;//边界 
	return mapp[ax][ay];
}

为了建图方便,我将状态存到一维数组里:
格子的编号:从上到下从左到右编

(e.g.)

  • 1 2 3 4
  • 5 6 7 8
  • ……

状态的编号:(e.g.)

1号格子的四个状态编号为0123

于是得到如下编号函数:

int getnum(int ax,int ay,int t){
	return ((ax-1)*m+ay)*4-(4-t);
}//t=0/1/2/3

由于(q)组询问的地图情况是相同的,我们可以先建图。

什么样的两个状态可以连边呢?

考虑两种情况:

·空白格子绕着(B)上下左右乱转

可以用bfs求出乱转最少多少次可以从上转到下,从左转到右...

需要注意的是,乱转时(B)是不能动的!!!(这个地方卡了好久QAQ)

·空白格子和(B)交换位置

必须保证两个状态都存在。

连上距离为1的边即可。

int bfs(int dx,int dy,int sx,int sy,int tx,int ty){//空白格子乱转的最小次数 
	//(sx,sy)出发到(tx,ty),不能经过(dx,dy)
	queue<white>q;
	memset(vis,0,sizeof vis);
	white st;st.x=sx;st.y=sy;
	st.step=0;vis[sx][sy]=1;
	q.push(st);
	while(!q.empty()){
		white noww=q.front();
		q.pop();
		if(noww.x==tx&&noww.y==ty)//到达目标格子 
		return noww.step;
		for(int i=0;i<4;i++){//四个方向乱转 
			if(judge(noww.x+xx[i],noww.y+yy[i])){//如果合法 
				if(vis[noww.x+xx[i]][noww.y+yy[i]])continue;//正在访问 
				if(noww.x+xx[i]==dx&&noww.y+yy[i]==dy)continue;//不能碰到目标棋子 
				white nxt;
				nxt.x=noww.x+xx[i];
				nxt.y=noww.y+yy[i];
				nxt.step=noww.step+1;
				q.push(nxt);vis[noww.x+xx[i]][noww.y+yy[i]]=1;
			}
		}
	}
	return inf;//到不了 
}

棋子不动,空白格乱转的情况 :

    for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++)
    for(int k=0;k<4;k++)
	for(int l=k+1;l<4;l++){
		if(ok[i][j][k]&&ok[i][j][l]){//必须都要合法 
			int aa=getnum(i,j,k);
			int bb=getnum(i,j,l);
			int cc=bfs(i,j,i+xx[k],j+yy[k],i+xx[l],j+yy[l]);
			if(cc==inf)continue;
			add(aa,bb,cc);add(bb,aa,cc);
		}
	}

qwq

	for(int i=1;i<=n;i++)//空白与目标棋子左右互换 
	for(int j=1;j<m;j++){
		if(ok[i][j][3]&&ok[i][j+1][2]){ //注意谁左谁右
			int aa=getnum(i,j,3);//别问我怎么知道的orz
			int bb=getnum(i,j+1,2);
			add(aa,bb,1);add(bb,aa,1);
		}
	}
	for(int i=1;i<n;i++)//上下互换 
	for(int j=1;j<=m;j++){
		if(ok[i][j][1]&&ok[i+1][j][0]){
			int aa=getnum(i,j,1);
			int bb=getnum(i+1,j,0);
			add(aa,bb,1);add(bb,aa,1);
		}
	}

这样我们的初始化就完成啦!
(加边的操作就和普通图论一样了w)

对于每一组询问,先特判一下:

while(q--){
		scanf("%d%d%d%d%d%d",&ex,&ey,&bx,&by,&cx,&cy);
		if(bx==cx&&by==cy){
			puts("0");
			continue;
		}
		if(!mapp[cx][cy]){
			puts("-1");
			continue;
		}
		if(!mapp[bx][by]){
			puts("-1");
			continue;
		}
		work();
	}

然后考虑,怎样把一开始的状态转移到图上呢?

因为图上只存在空白格与(B) 相邻的状况,so暴力尝试让空白格跑到(B)的四个方向就好啦

    for(int i=0;i<4;i++){//空白走到目标棋子旁边 
		if(judge(bx+xx[i],by+yy[i])){//这个点可以走 
			int nw=bfs(bx,by,ex,ey,bx+xx[i],by+yy[i]);
			if(nw==inf)continue;//走不到 
			int nq=getnum(bx,by,i);
			d[nq]=nw;
			Q.push(nq);
			viss[nq]=1;
		}
	}

然后,愉快地跑SPFA

    while(!Q.empty()){
		int noww=Q.front();Q.pop();viss[noww]=0;
		for(int j=head[noww];j;j=b[j].nxt){
			int vv=b[j].to;
			if(d[vv]>d[noww]+b[j].dis){
				d[vv]=d[noww]+b[j].dis;
				if(!viss[vv])Q.push(vv),viss[vv]=1;
			}
		}
	}

最后,愉快地检查是否能到(C)

    int ans=inf;
	for(int i=0;i<4;i++){
		int qaq=getnum(cx,cy,i);
		ans=min(ans,d[qaq]);
	}
	if(ans==inf)puts("-1");
	else printf("%d
",ans);

贴无注释代码QWQ

#include<bits/stdc++.h>
using namespace std;
const int MAXN=35;
const int inf=99999999;
int n,m,q,ex,ey,bx,by,cx,cy;
bool mapp[MAXN][MAXN];
int xx[4]={-1,1,0,0};
int yy[4]={0,0,-1,1};
int getnum(int ax,int ay,int t){
	return ((ax-1)*m+ay)*4-(4-t);
} 
struct white{
	int x,y;
	int step;
};
bool judge(int ax,int ay){
	if(ax<=0||ax>n||ay<=0||ay>m)return 0;
	return mapp[ax][ay];
}
bool vis[MAXN][MAXN];
int bfs(int dx,int dy,int sx,int sy,int tx,int ty){
	queue<white>q;
	memset(vis,0,sizeof vis);
	white st;st.x=sx;st.y=sy;
	st.step=0;vis[sx][sy]=1;
	q.push(st);
	while(!q.empty()){
		white noww=q.front();
		q.pop();
		if(noww.x==tx&&noww.y==ty)
		return noww.step;
		for(int i=0;i<4;i++){
			if(judge(noww.x+xx[i],noww.y+yy[i])){
				if(vis[noww.x+xx[i]][noww.y+yy[i]])continue;
				if(noww.x+xx[i]==dx&&noww.y+yy[i]==dy)continue; 
				white nxt;
				nxt.x=noww.x+xx[i];
				nxt.y=noww.y+yy[i];
				nxt.step=noww.step+1;
				q.push(nxt);vis[noww.x+xx[i]][noww.y+yy[i]]=1;
			}
		}
	}
	return inf;
}
struct edge{
	int to,nxt,dis;
}b[5005];
int head[5005],tot;
void add(int u,int v,int w){
	b[++tot].to=v;b[tot].nxt=head[u];
	b[tot].dis=w;head[u]=tot;
}
bool ok[MAXN][MAXN][5];
void init(){
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++){
		if(!mapp[i][j])continue;
		for(int k=0;k<4;k++)
		if(judge(i+xx[k],j+yy[k]))ok[i][j][k]=1;
		}
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++)
    for(int k=0;k<4;k++)
	for(int l=k+1;l<4;l++){
		if(ok[i][j][k]&&ok[i][j][l]){
			int aa=getnum(i,j,k);
			int bb=getnum(i,j,l);
			int cc=bfs(i,j,i+xx[k],j+yy[k],i+xx[l],j+yy[l]);
			if(cc==inf)continue;
			add(aa,bb,cc);add(bb,aa,cc);
		}
	}
	for(int i=1;i<=n;i++)
	for(int j=1;j<m;j++){
		if(ok[i][j][3]&&ok[i][j+1][2]){ 
			int aa=getnum(i,j,3);
			int bb=getnum(i,j+1,2);
			add(aa,bb,1);add(bb,aa,1);
		}
	}
	for(int i=1;i<n;i++)
	for(int j=1;j<=m;j++){
		if(ok[i][j][1]&&ok[i+1][j][0]){
			int aa=getnum(i,j,1);
			int bb=getnum(i+1,j,0);
			add(aa,bb,1);add(bb,aa,1);
		}
	}
}
queue<int>Q;
bool viss[5005];
int d[5005];
void work(){
	memset(d,128/3,sizeof d);
	memset(viss,0,sizeof viss);
	for(int i=0;i<4;i++){
		if(judge(bx+xx[i],by+yy[i])){
			int nw=bfs(bx,by,ex,ey,bx+xx[i],by+yy[i]);
			if(nw==inf)continue; 
			int nq=getnum(bx,by,i);
			d[nq]=nw;
			Q.push(nq);
			viss[nq]=1;
		}
	}
	while(!Q.empty()){
		int noww=Q.front();Q.pop();viss[noww]=0;
		for(int j=head[noww];j;j=b[j].nxt){
			int vv=b[j].to;
			if(d[vv]>d[noww]+b[j].dis){
				d[vv]=d[noww]+b[j].dis;
				if(!viss[vv])Q.push(vv),viss[vv]=1;
			}
		}
	}
	int ans=inf;
	for(int i=0;i<4;i++){
		int qaq=getnum(cx,cy,i);
		ans=min(ans,d[qaq]);
	}
	if(ans==inf)puts("-1");
	else printf("%d
",ans);
}
int main(){
	scanf("%d%d%d",&n,&m,&q);
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++)cin>>mapp[i][j];
	init();
	while(q--){
		scanf("%d%d%d%d%d%d",&ex,&ey,&bx,&by,&cx,&cy);
		if(bx==cx&&by==cy){
			puts("0");
			continue;
		}
		if(!mapp[cx][cy]){
			puts("-1");
			continue;
		}
		if(!mapp[bx][by]){
			puts("-1");
			continue;
		}
		work();
	}
	return 0;
}

完结撒花!!!

_by Erutsiom _

原文地址:https://www.cnblogs.com/erutsiom/p/9904567.html