华容道

【问题描述】

小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;
}

 

posted @ 2016-08-19 11:36  前前前世。  阅读(384)  评论(0编辑  收藏  举报