华容道
【问题描述】
小B最近迷上了华容道,可是他总是要花很长的时间才能完成一次。于是,他想到用编程来完成华容道:给定一种局面,华容道是否根本就无法完成,如果能完成,最少需要多少时间。
小B玩的华容道与经典的华容道游戏略有不同,游戏规则是这样的:
(1)在一个n*m棋盘上有n*m个格子,其中有且只有一个格子是空白的,其余n*m-1个格子上每个格子上有一个棋子,每个棋子的大小都是1*1的;
(2)有些棋子是固定的,有些棋子则是可以移动的;
(3)任何与空白的格子相邻(有公共的边)的格子上的棋子都可以移动到空白格子上;
游戏的目的是把某个指定位置可以活动的棋子移动到目标位置。
给定一个棋盘,游戏可以玩q次,当然,每次棋盘上固定的格子是不会变的,但是棋盘上空白的格子的初始位置、指定的可移动的棋子的初始位置和目标位置却可能不同。第i次玩的时候,空白的格子在第EXi行第EYi列,指定的可移动棋子的初始位置为第SXi行第SYi列,目标位置为第TXi行第TYi列。
假设小B每秒钟能进行一次移动棋子的操作,而其他操作的时间都可以忽略不计。请你告诉小B每一次游戏所需要的最少时间,或者告诉他不可能完成游戏。
【输入描述】
第一行有3个整数,每两个整数之间用一个空格隔开,依次表示n、m和q;
接下来的n行描述一个n*m的棋盘,每行有m个整数,每两个整数之间用一个空格隔开,每个整数描述棋盘上一个格子的状态,0表示该格子上的棋子是固定的,1表示该格子上的棋子可以移动或者该格子是空白的。接下来的q行,每行包含6个整数依次是 EXi、EYi、SXi、SYi、TXi、TYi,每两个整数之间用一个空格隔开,表示每次游戏空白格子的位置,指定棋子的初始位置和目标位置。
【输出描述】
输出有q行,每行包含1个整数,表示每次游戏所需要的最少时间,如果某次游戏无法完成目标则输出−1。
【输入样例】
3 4 2
0 1 1 1
0 1 1 0
0 1 0 0
3 2 1 2 2 2
1 2 2 2 3 2
【输出样例】
2
-1
【数据范围及提示】
棋盘上划叉的格子是固定的,红色格子是目标位置,圆圈表示棋子,其中绿色圆圈表示目标棋子。
(1)第一次游戏,空白格子的初始位置是(3,2)(图中空白所示),游戏的目标是将初始位置在(1,2)上的棋子(图中绿色圆圈所代表的棋子)移动到目标位置(2,2)(图中红色的格子)上。
移动过程如下:
(2)第二次游戏,空白格子的初始位置是(1,2)(图中空白所示),游戏的目标是将初始位置在(2,2)上的棋子(图中绿色圆圈所示)移动到目标位置(3,2)上。
要将指定块移入目标位置,必须先将空白块移入目标位置,空白块要移动到目标位置,必然是从位置(2,2)上与当前图中目标位置上的棋子交换位置,之后能与空白块交换位置的只有当前图中目标位置上的那个棋子,因此目标棋子永远无法走到它的目标位置, 游戏无法完成。
对于30%的数据,1 ≤ n,m ≤ 10,q = 1;
对于60%的数据,1 ≤ n,m ≤ 30,q ≤ 10;
对于100%的数据,1 ≤ n,m ≤ 30,q ≤ 500。
连黄学长都弃坑了,本蒟蒻还能干什么(70分BFS):
源代码: #include<cstdio> #include<cstring> int n,m,P; bool Map[31][31],Mark[31][31][31][31]; int x[4]={0,0,1,-1},y[4]={1,-1,0,0}; struct Node { int EX,EY,SX,SY,Sum; }Q[810001]={0}; //坑爹1者也,不要傻傻地相信Windows。 bool Judge(int X,int Y,int T1,int T2) { if (T1<1||T2<1||T1>n||T2>m||!Map[T1][T2]) //坑爹2者也,注意在Search()中,空白点与指定点已交换位置。 return false; if (Mark[X][Y][T1][T2]) //已经有过此种情况的移动,那么再怎么移动不过白费力气,舍去。 return false; Mark[X][Y][T1][T2]=true; return true; } void Search() { memset(Mark,0,sizeof(Mark)); //经过灵活严谨的题目分析,可得Mark[X1][Y1][X2][Y2]状态是唯一的,值得学习借鉴。 int Head(0),Tail(0); int X,Y,T1,T2,TX,TY; scanf("%d%d%d%d%d%d",&Q[0].EX,&Q[0].EY,&Q[0].SX,&Q[0].SY,&TX,&TY); if (TX==Q[0].SX&&TY==Q[0].SY) //特判。 { printf("0\n"); return; } Mark[Q[0].SX][Q[0].SY][Q[0].EX][Q[0].EY]=true; //表示已经遍历过此情况。 while (Head<=Tail) //BFS。 { for (int a=0;a<4;a++) { X=Q[Head].SX; Y=Q[Head].SY; //指定块的位置。 T1=Q[Head].EX+x[a]; T2=Q[Head].EY+y[a]; //空白块移动。 if (X==T1&&Y==T2) //如果空白块能移动到指定块,移动指定块到空白块。 { X=Q[Head].EX; Y=Q[Head].EY; } if (Judge(X,Y,T1,T2)) //如果该状态符合条件,入队。 { Tail++; Q[Tail].SX=X; Q[Tail].SY=Y; Q[Tail].EX=T1; Q[Tail].EY=T2; Q[Tail].Sum=Q[Head].Sum+1; if (X==TX&&Y==TY) //如果到达指定位置输出移动步数即时间。 { printf("%d\n",Q[Tail].Sum); return; } } } Head++; } printf("-1\n"); return; } int main() { scanf("%d%d%d",&n,&m,&P); for (int a=1;a<=n;a++) for (int b=1;b<=m;b++) scanf("%d",&Map[a][b]); for (int a=1;a<=P;a++) Search(); return 0; }
正解(BFS+SPFA):
源代码: #include<cstdio> #include<cstring> #include<queue> #include<stack> #define INF 0x3f3f3f3f using namespace std; struct Node1 { int X,Y; }; struct Node2 { int X,Y,Now; }; queue <Node1> Q1; queue <Node2> Q2; stack <Node2> st; int m,n,P,EX,EY,SX,SY,TX,TY; int Map[31][31],i[31][31],Step[31][31][5][5],inq[31][31][5],DP[31][31][4]; int x[4]={0,1,0,-1},y[4]={1,0,-1,0}; void Pre_BFS(int X,int Y) //预处理出(sx,sy)这个格子周围四个格子间不经过(X,Y)的最短路。 { for (int a=0;a<4;a++) { int T1=X+x[a]; int T2=Y+y[a]; if (T1>0&&T2>0&&T1<=n&&T2<=m&&Map[T1][T2]) st.push((Node2){T1,T2,a}); } while (!st.empty()) //需要一个一个处理。 { while (!Q1.empty()) Q1.pop(); memset(i,INF,sizeof(i)); int T1=st.top().X; int T2=st.top().Y; int Now=st.top().Now; i[T1][T2]=0; Q1.push((Node1){T1,T2}); st.pop(); while (!Q1.empty()) { Node1 T=Q1.front(); Q1.pop(); int XXX=T.X; int YYY=T.Y; for (int a=0;a<4;a++) { int S1=XXX+x[a]; int S2=YYY+y[a]; if (S1>0&&S2>0&&S1<=n&&S2<=m&&Map[S1][S2]&&(S1!=X||S2!=Y)&&i[S1][S2]==INF) //由于路权都为1,所以第一次到达即为最短路。 { i[S1][S2]=i[XXX][YYY]+1; Q1.push((Node1){S1,S2}); } } } for (int a=0;a<4;a++) { int S1=X+x[a]; int S2=Y+y[a]; if (S1>0&&S2>0&&S1<=n&&S2<=m&&Map[S1][S2]&&(S1!=T1||S2!=T2)&&i[T1][T2]!=INF) Step[X][Y][Now][a]=i[S1][S2]; //记录最短路。 } } } void Pre_Black() //找当前的空格到棋子周围的空格的最短路。 { while (!Q1.empty()) Q1.pop(); memset(i,0x3f,sizeof(i)); i[EX][EY]=0; Q1.push((Node1){EX,EY}); while(!Q1.empty()) { Node1 T=Q1.front(); Q1.pop(); for (int a=0;a<4;a++) { int T1=T.X+x[a]; int T2=T.Y+y[a]; if (T1>0&&T2>0&&T1<=n&&T2<=m&&Map[T1][T2]&&(T1!=SX||T2!=SY)&&i[T1][T2]==INF) //不能经过(SX,SY)这个点。 { i[T1][T2]=i[T.X][T.Y]+1; Q1.push((Node1){T1,T2}); } } } } int BFS() //个人觉得叫SPFA()更合适些。 { if (TX==SX&&TY==SY) return 0; //起点与终点重合,返回0。 Pre_Black(); //预处理。 memset(DP,INF,sizeof(DP)); while (!Q2.empty()) Q2.pop(); for (int a=0;a<4;a++) { int T1=SX+x[a]; int T2=SY+y[a]; if (T1>0&&T2>0&&T1<=n&&T2<=m&&i[T1][T2]!=INF&&Map[T1][T2]) { DP[SX][SY][a]=i[T1][T2]; //加入队列。 Q2.push((Node2){SX,SY,a}); inq[SX][SY][a]=1; } } while(!Q2.empty()) { Node2 T=Q2.front(); Q2.pop(); inq[T.X][T.Y][T.Now]=0; //棋子移到空格的情况。 int T1=T.X+x[T.Now]; int T2=T.Y+y[T.Now]; if (DP[T1][T2][(T.Now+2)%4]>DP[T.X][T.Y][T.Now]+1) { DP[T1][T2][(T.Now+2)%4]=DP[T.X][T.Y][T.Now]+1; if (!inq[T1][T2][(T.Now+2)%4]) { inq[T1][T2][(T.Now+2)%4]=1; Q2.push((Node2){T1,T2,(T.Now+2)%4}); } } //棋子周围的空格互相移动的情况。 for (int a=0;a<4;a++) { T1=T.X+x[a]; T2=T.Y+y[a]; if (T1>0&&T2>0&&T1<=n&&T2<=m&&Map[T1][T2]&&a!=T.Now&&Step[T.X][T.Y][T.Now][a]!=INF) if (DP[T.X][T.Y][a]>DP[T.X][T.Y][T.Now]+Step[T.X][T.Y][T.Now][a]) { DP[T.X][T.Y][a]=DP[T.X][T.Y][T.Now]+Step[T.X][T.Y][T.Now][a]; if (!inq[T.X][T.Y][a]) { inq[T.X][T.Y][a]=1; Q2.push((Node2){T.X,T.Y,a}); } } } } int ans=INF; for (int a=0;a<4;a++) //找最短路。 ans=min(ans,DP[TX][TY][a]); if (ans==INF) return -1; //不存在。 return ans; } int main() { scanf("%d%d%d",&n,&m,&P); for (int a=1;a<=n;a++) for(int b=1;b<=m;b++) scanf("%d",&Map[a][b]); memset(Step,INF,sizeof(Step)); for (int a=1;a<=n;a++) for (int b=1;b<=m;b++) if (Map[a][b]) Pre_BFS(a,b); //预处理。 for (int a=1;a<=P;a++) { scanf("%d%d%d%d%d%d",&EX,&EY,&SX,&SY,&TX,&TY); printf("%d\n",BFS()); } return 0; }