NOIP2013 华容道 解题报告

描述

小 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 个整数依次是 EXiEYiSXiSYiTXiTYi,每两个整数之间用一个空格隔开,表示每次游戏空白格子的位置,指定棋子的初始位置和目标位置。

输出格式

输出有 q 行,每行包含 1 个整数,表示每次游戏所需要的最少时间,如果某次游戏无法完成目标则输出−1。

样例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

样例输出1[复制]

2 
-1

限制

每个测试点1s。

提示

样例说明

棋盘上划叉的格子是固定的,红色格子是目标位置,圆圈表示棋子,其中绿色圆圈表示目标棋子。

  1. 第一次游戏,空白格子的初始位置是 (3, 2)(图中空白所示),游戏的目标是将初始位置在(1, 2)上的棋子(图中绿色圆圈所代表的棋子)移动到目标位置(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(x0)and(y

           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(x10)and(y1

                 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(x10)and(y1

        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.

你可能感兴趣的:(NOIP解题报告)