小 B 最近迷上了华容道,可是他总是要花很长的时间才能完成一次。于是,他想到用编程来完成华容道:给定一种局面,华容道是否根本就无法完成,如果能完成,最少需要多少时间。
小 B 玩的华容道与经典的华容道游戏略有不同,游戏规则是这样的:
在一个 n*m 棋盘上有 n*m 个格子,其中有且只有一个格子是空白的,其余 n*m-1个格子上每个格子上有一个棋子,每个棋子的大小都是 1*1 的;
有些棋子是固定的,有些棋子则是可以移动的;
任何与空白的格子相邻(有公共的边)的格子上的棋子都可以移动到空白格子上。 游戏的目的是把某个指定位置可以活动的棋子移动到目标位置。
给定一个棋盘,游戏可以玩 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
每个测试点1s。
棋盘上划叉的格子是固定的,红色格子是目标位置,圆圈表示棋子,其中绿色圆圈表示目标棋子。
第一次游戏,空白格子的初始位置是 (3, 2)(图中空白所示),游戏的目标是将初始位置在(1, 2)上的棋子(图中绿色圆圈所代表的棋子)移动到目标位置(2, 2)(图中红色的格子)上。
移动过程如下:
第二次游戏,空白格子的初始位置是(1, 2)(图中空白所示),游戏的目标是将初始位置在(2, 2)上的棋子(图中绿色圆圈所示)移动到目标位置 (3, 2)上。
要将指定块移入目标位置,必须先将空白块移入目标位置,空白块要移动到目标位置,必然是从位置(2,2)上与当前图中目标位置上的棋子交换位置,之后能与空白块交换位置的只有当前图中目标位置上的那个棋子,因此目标棋子永远无法走到它的目标位置,游戏无法完成。
(1) 解题思路
这个题给一些能移的棋子和一个空格,要求目标棋子是否能移到制定位置。
本题有两种解法,一种是宽搜+剪枝,一种是宽搜预处理+SPFA。下面的代码是宽搜预处理+SPFA。
首先,如果要移动目标棋子,那么我们首先必须要将空格移到该棋子的上下左右四个方
向上相邻位置之一,然后才可以移动该棋子。 然后,我们分析该棋子移动时候的性质: 棋子每次可以移动,仅当空格位于其相邻位置的时候,每次移动完棋子,空格总会在棋
子相邻的位置,那么我们发现,对于棋子在某一位置,然后空格又在其四个方向上某一
相邻位置时,棋子要想某一方向移动一个时的花费的步数是一定的,那么,就可以先进
行一次预处理,预处理出对于目标棋子在上述条件下每次移动所需的步数。然后,预处理完成之后,我们会发现每次查询都会变成一个求最短路的问题,用Dijstra或SPFA的话,可以在时限范围内解决。(SPFA较好)
实现:
定义一个数组f[x][y][k][h],表示目标棋子在位置(x,y)且空格在目标棋子的k方向上的相邻格子时,目标棋子往h方向移动1格所需的步数,然后用状态[x][y][k]作为节点建图,用各个状态的关系连边,每次询问时重新定义一个源点跟终点,跑最短路就可以得出答案。
(预处理时跑n2次O(n2)的BFS就可以了)
(2) 代码详解
const u:array[1..4] of integer=(-1,0,1,0); //四种移法
v:array[1..4] of integer=(0,1,0,-1);
oo=100000000;
type node=record
x,y,cs,l:longint;//x记录空格可以移到的横坐标,y记录纵坐标,cs记录空格几步可以这个坐标
end;
var ft:text;
n,m,q,ii,i,i1,j1,j,l,w,x,y,x1,y1,x2,y2,x3,y3,es,ey,wei,tou,kx,ky,ans:longint;
r:array[0..5000000] of node;
aa,a,fb,fb2:array[0..31,0..31] of longint;//fb2只是用来重置fb数组的,fb[i,j]数组用来记录空格几步移到(i,j)位置,a是记录初始状态的
ff,z,z2:array[0..31,0..31,1..4] of longint;
f:array[0..31,0..31,1..4,1..4] of longint;//见解题报告
procedure bfs(x,y:longint); //宽搜预处理,记录出从每个格子可以向哪个方向移动几步
var i,x1,y1:longint;
begin
wei:=1;
tou:=0;
r[wei].x:=x; //r.x用来记录可以移动的横坐标
r[wei].y:=y; //纵坐标
r[wei].cs:=fb[x,y]; //几步可以移到
while tou begin inc(tou); for i:=1 to 4 do //是否可以再向别的点移动 begin x1:=r[tou].x+u[i]; y1:=r[tou].y+v[i]; if (a[x1,y1]=1)and(r[tou].cs+1 begin inc(wei); r[wei].x:=x1; r[wei].y:=y1;//将新到达的点入队 r[wei].cs:=r[tou].cs+1;//更新当前的节点的步数 fb[x1,y1]:=r[wei].cs; //记录(等过程结束移到f四维数组里) end; end; end; end; begin readln(n,m,q); //读入方阵 fori:=1 to n do begin for j:=1 to m do read(a[i,j]); readln; end; aa:=a;//将棋盘存储到aa fori:=1 to n do //初始化 for j:=1 to m do begin fb2[i,j]:=oo; for l:=1 to 4 do begin z2[i,j,l]:=oo; for w:=1 to 4 do f[i,j,l,w]:=oo;//初始化时,每个方格旁边的每个方向上的空格向每个方向移动的距离都是oo end; end; fori:=1 to n do //预处理 for j:=1 to m do if a[i,j]=1 then //如果该棋子可以移动 for l:=1 to 4 do //四个方向 begin x:=i+u[l]; y:=j+v[l]; if (x>0)and(x begin a[x,y]:=0; //已被移动过,防止重复移动 fb:=fb2; //重置 fb[i,j]:=1; //空格移到这个位置 bfs(i,j);//宽搜处理出这个可以移动的节点到达周围4个方向的最小步数 for w:=1 to 4 do begin x1:=x+u[w]; y1:=y+v[w]; if(x1>0)and(x1 begin f[i,j,l,w]:=fb[x1,y1]; //记录表示目标棋子在位置(x,y)且空格在目标棋子的k方向上的相邻格子时,目标棋子往h方向移动1格所需的步数 end; end; a[x,y]:=1; //取消当前节点的标记,进入下一层循环 end; end; forii:=1 to q do begin read(kx,ky,x,y,es,ey); if (x=es)and(y=ey) then //如果初始位置与目标位置相同 begin writeln(0); continue; end; fb:=fb2; //重置空格能到的地方的值 a[x,y]:=0; //空格不能移到这个点 fb[kx,ky]:=0; //空格移动0步到 bfs(kx,ky); //空格能到的地方 wei:=0;清空队列,重新开始存入节点 z:=z2; //重置 fillchar(ff,sizeof(ff),0); for j:=1 to 4 do //目标棋子向四周移动 begin x1:=x+u[j]; y1:=y+v[j]; if (x1>0)and(x1 begin //记录 inc(wei); r[wei].x:=x; r[wei].y:=y;//入队 r[wei].l:=j; //表示方向 z[x,y,j]:=fb[x1,y1]; //某位置向某方向最多可以移动几步 ff[x,y,j]:=1; //某位置向某方向可以移动 end; end; tou:=0; whiletou begin inc(tou); for i:=1 to 4 do begin x2:=r[tou].x; //空格可以移动的第一步 y2:=r[tou].y; x1:=x2+u[r[tou].l]+u[i]; //向某方向 y1:=y2+v[r[tou].l]+v[i]; x3:=x2+u[r[tou].l]; y3:=y2+v[r[tou].l]; if (f[x2,y2,r[tou].l,i]<>oo){可以移动}and(z[x3,y3,i]>z[x2,y2,r[tou].l]+f[x2,y2,r[tou].l,i]){可以松弛} then begin z[x3,y3,i]:=z[x2,y2,r[tou].l]+f[x2,y2,r[tou].l,i]; if ff[x3,y3,i]=0 then //没有记录 begin ff[x3,y3,i]:=1; inc(wei); r[wei].x:=x3; r[wei].y:=y3; r[wei].l:=i; end; end; end; ff[r[tou].x,r[tou].y,r[tou].l]:=0; //回溯 end; ans:=oo; for i:=1 to 4 do 查找 if z[es,ey,i] if ans=oo then writeln(-1) else writeln(ans); a[x,y]:=1; //回溯 end; end.