首页 > 技术文章 > Luogu P1979 华容道(bfs+最短路)

coder-Uranus 2018-10-11 18:28 原文

P1979 华容道

题意

题目描述

小B最近迷上了华容道,可是他总是要花很长的时间才能完成一次。于是,他想到用编程来完成华容道:给定一种局面,华容道是否根本就无法完成,如果能完成, 最少需要多少时间。

小B玩的华容道与经典的华容道游戏略有不同,游戏规则是这样的:

在一个\(n \times m\)棋盘上有\(n \times m\)个格子,其中有且只有一个格子是空白的,其余\(n \times (m-1)\)个格子上每个格子上有一个棋子,每个棋子的大小都是\(1 \times 1\)的;

有些棋子是固定的,有些棋子则是可以移动的;

任何与空白的格子相邻(有公共的边)的格子上的棋子都可以移动到空白格子上。

游戏的目的是把某个指定位置可以活动的棋子移动到目标位置。

给定一个棋盘,游戏可以玩\(q\)次,当然,每次棋盘上固定的格子是不会变的,但是棋盘上空白的格子的初始位置、指定的可移动的棋子的初始位置和目标位置却可能不同。第\(i\)次玩的时候,空白的格子在第\(EX_i\)行第\(EY_i\)列,指定的可移动棋子的初始位置为第\(SX_i\)行第\(SY_i\)列,目标位置为第\(TX_i\)行第\(TY_i\)列。

假设小B每秒钟能进行一次移动棋子的操作,而其他操作的时间都可以忽略不计。请你告诉小B每一次游戏所需要的最少时间,或者告诉他不可能完成游戏。

输入输出格式

输入格式:

第一行有\(3\)个整数,每两个整数之间用一个空格隔开,依次表示\(n,m,q\)

接下来的\(n\)行描述一个\(n \times m\)的棋盘,每行有\(m\)个整数,每两个整数之间用一个空格隔开,每个整数描述棋盘上一个格子的状态,\(0\)表示该格子上的棋子是固定的,\(1\)表示该格子上的棋子可以移动或者该格子是空白的。

接下来的\(q\)行,每行包含\(6\)个整数依次是\(EX_i,EY_i,SX_i,SY_i,TX_i,TY_i\),每两个整数之间用一个空格隔开,表示每次游戏空白格子的位置,指定棋子的初始位置和目标位置。

输出格式:

\(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

说明

【输入输出样例说明】

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

第一次游戏,空白格子的初始位置是\((3,2)\)(图中空白所示),游戏的目标是将初始位置在\((1,2)\)上的棋子(图中绿色圆圈所代表的棋子)移动到目标位置\((2,2)\)(图中红色的格子)上。

移动过程如下:

P1979-1

第二次游戏,空白格子的初始位置是\((1,2)\)(图中空白所示),游戏的目标是将初始位置在\((2,2)\)上的棋子(图中绿色圆圈所示)移动到目标位置\((3,2)\)上。

P1979-2

要将指定块移入目标位置,必须先将空白块移入目标位置,空白块要移动到目标位置,必然是从位置\((2,2)\)上与当前图中目标位置上的棋子交换位置,之后能与空白块交换位置的只有当前图中目标位置上的那个棋子,因此目标棋子永远无法走到它的目标位置,游戏无法完成。

【数据范围】

对于\(30 \%\)的数据,\(1 \leq n,m \leq 10,q=1\)

对于\(60 \%\)的数据,\(1 \leq n,m \leq 30,q \leq 10\)

对于\(100 \%\)的数据,\(1 \leq n,m \leq 30,q \leq 500\)

思路

怨念--;

这显然是一道爆搜题,只需要搜索就能过了。

搜个锤子啊,要硬搜能过这题还会成为\(NOIP \ 2013\)的毒瘤题?我打的暴力只有\(25\)分,然而logeadd大佬有\(80\)分。

可不可以优化一下呢?其实这道题在\(bfs\)的过程中只需要记录空白格子所在位置以及需要移动的那颗棋子的位置就好了,所以我们不妨把每个\((sx,sy,ex,ey)\)状态看作一个结点,把他向所有可以转移到的点连边,当\((sx,sy)\)转移到\((tx,ty)\)\((ex,ey)\)转移到终点周围的某个格子时,统计答案,那么我们就可以用\(SPFA\)来写。

但是这样的\(SPFA\)边权只有\(1\),不就相当于是优秀一点的\(bfs\)吗?相当于我们没有做到任何的优化啊(虽然我依靠这个卡到了\(80\)分)。

能不能精简一点我们点的数量呢?

想想看,要是我们玩华容道的话,会怎么做?首先我们要把空格子“运”到那颗棋子(叫它钦定棋子好了)旁边,然后钦定棋子移动到空格子上,空格子再在保证钦定棋子位置不改变的情况下,移动到它的前面,继续“运”...

诶,那有效的状态不就是空格子在钦定棋子旁边的状态吗?这样,我们状态记录就可以变成\((sx,sy,k)\)\(k\)表示在钦定棋子的哪个方位。点的数量一下子就从\(n^4\)变成了\(n^2\)级别的。

具体怎么办呢?对于每一个位置,我们统计空格子不经过钦定棋子到达它的另一方位的最短路径,再建空格子与钦定棋子交换这一操作的边,就可以跑\(n^2\)级别的\(SPFA\)了。

AC代码

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
const int MAXN=5005;
int n,m,q,G[35][35],dis[35][35],d[MAXN];
int cnt,top[MAXN],to[MAXN<<3],len[MAXN<<3],nex[MAXN<<3];
bool vis[MAXN];
int a[4]={-1,+0,+1,+0};
int b[4]={+0,+1,+0,-1};
inline int read()
{
    int re=0;
    char ch=getchar();
    while(!isdigit(ch)) ch=getchar();
    while(isdigit(ch)) re=(re<<3)+(re<<1)+ch-'0',ch=getchar();
    return re;
}
inline int f(int x,int y){return (x-1)*m*4+(y-1)*4;}
inline void add_edge(int x,int y,int z){to[++cnt]=y,len[cnt]=z,nex[cnt]=top[x],top[x]=cnt;}
void bfs(int ex,int ey,int sx,int sy,int z)
{
    memset(dis,-1,sizeof dis);
    dis[sx][sy]=1,dis[ex][ey]=0;
    queue<PII>Q;
    Q.push(make_pair(ex,ey));
    while(!Q.empty())
    {
        int x=Q.front().first,y=Q.front().second;Q.pop();
        for(int i=0;i<4;i++)
        {
            int dx=x+a[i],dy=y+b[i];
            if(!G[dx][dy]||dis[dx][dy]!=-1) continue;
            dis[dx][dy]=dis[x][y]+1;
            Q.push(make_pair(dx,dy));
        }
    }
    if(z==4) return ;
    int id=f(sx,sy);
    for(int i=0;i<4;i++)
        if(dis[sx+a[i]][sy+b[i]]>0)
            add_edge(id+z,id+i,dis[sx+a[i]][sy+b[i]]);
    add_edge(id+z,f(ex,ey)+(z+2)%4,1);
}
void SPFA(int sx,int sy)
{
    queue<int>Q;
    memset(d,0x3f,sizeof d);
    int id=f(sx,sy);
    for(int i=0;i<4;i++)
        if(dis[sx+a[i]][sy+b[i]]!=-1)
        {
            vis[id+i]=true;
            d[id+i]=dis[sx+a[i]][sy+b[i]];
            Q.push(id+i);
        }
    while(!Q.empty())
    {
        int now=Q.front();Q.pop();
        vis[now]=false;
        for(int i=top[now];i;i=nex[i])
            if(d[to[i]]>d[now]+len[i])
            {
                d[to[i]]=d[now]+len[i];
                if(!vis[to[i]])
                {
                    vis[to[i]]=true;
                    Q.push(to[i]);
                }
            }
    }
}
int main()
{
    n=read(),m=read(),q=read();
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            G[i][j]=read();
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            if(G[i][j])
                for(int k=0;k<4;k++)
                    if(G[i+a[k]][j+b[k]])
                        bfs(i+a[k],j+b[k],i,j,k);
    while(q--)
    {
        int ex=read(),ey=read(),sx=read(),sy=read(),tx=read(),ty=read();
        if(sx==tx&&sy==ty)
        {
            puts("0");
            continue;
        }
        bfs(ex,ey,sx,sy,4);
        SPFA(sx,sy);
        int id=f(tx,ty);
        int ans=0x3f3f3f3f;
        for(int i=0;i<4;i++) ans=min(ans,d[id+i]);
        printf("%d\n",ans==0x3f3f3f3f?-1:ans);
    }
    return 0;
}

推荐阅读