【NOIP2013】华容道 SPFA+BFS预处理

9 篇文章 0 订阅

最近没什么时间搞奥赛,所以这个题目看题解什么的弄了好久才懂。(懂了没?)可能吧,反正是AC了。
心疼一下2013年的学长学姐们那一年题目都那么难
废话不多说了。我想写一篇题解尽量阐述清这个题目怎么写
CODEVS
题目描述 Description
小 B 最近迷上了华容道,可是他总是要花很长的时间才能完成一次。于是,他想到用编程来完成华容道:给定一种局面,华容道是否根本就无法完成,如果能完成,最少需要多少时间。
小 B 玩的华容道与经典的华容道游戏略有不同,游戏规则是这样的:

在一个 n*m 棋盘上有 n*m 个格子,其中有且只有一个格子是空白的,其余 n*m-1个格子上每个格子上有一个棋子,每个棋子的大小都是 1*1 的;
有些棋子是固定的,有些棋子则是可以移动的;
任何与空白的格子相邻(有公共的边)的格子上的棋子都可以移动到空白格子上。 游戏的目的是把某个指定位置可以活动的棋子移动到目标位置。
给定一个棋盘,游戏可以玩 q 次,当然,每次棋盘上固定的格子是不会变的,但是棋盘上空白的格子的初始位置、指定的可移动的棋子的初始位置和目标位置却可能不同。第 i 次玩的时候,空白的格子在第 EX_i 行第 EY_i 列,指定的可移动棋子的初始位置为第 SX_i 行第 SY_i 列,目标位置为第 TX_i 行第 TY_i 列。
假设小 B 每秒钟能进行一次移动棋子的操作,而其他操作的时间都可以忽略不计。请你告诉小 B 每一次游戏所需要的最少时间,或者告诉他不可能完成游戏。

输入描述 Input Description
第一行有 3 个整数,每两个整数之间用一个空格隔开,依次表示 n、m 和 q;
接下来的 n 行描述一个 n*m 的棋盘,每行有 m 个整数,每两个整数之间用一个空格隔开,每个整数描述棋盘上一个格子的状态,0 表示该格子上的棋子是固定的,1 表示该格子上的棋子可以移动或者该格子是空白的。
接下来的 q 行,每行包含 6 个整数依次是 EX_i、EY_i、SX_i、SY_i、TX_i、TY_i,每两个整数之间用一个空格隔开,表示每次游戏空白格子的位置,指定棋子的初始位置和目标位置。

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

样例输入 Sample Input

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

样例输出 Sample Output

2 
-1

【数据范围】
对于 30%的数据,1 ≤ n, m ≤ 10,q = 1;
对于 60%的数据,1 ≤ n, m ≤ 30,q ≤ 10;
对于 100%的数据,1 ≤ n, m ≤ 30,q ≤ 500。

题解
因为过程较为复杂所以采用总-分-总的结构阐述这个题解。
总:
首先这个题目如果是裸地跑SPFA或者是BFS都是过不了的具体原因参照
chrt的博客有关于各种方法时间复杂度的详细分析。

然后我们就需要对这个图进行预处理,因为我们每一次跑的图是没变的,变的只是起点,中转站,和终点。所以我们就先对这个图进行预处理来减少重复的工作以减小时间复杂度。具体的我们只用预处理出一个定义的数组。

STEP[i][j][k][h]:这个数组的含义代表在 中心棋子[i][j]不移动的前提下将在K方向上的空格移动至H方向所需要的步骤数。(此处以及下文所有的K方向以及H方向均是指与中心棋子[i][j]相邻的k方向的位置和h方向的位置)

然后再发现一个伟大而又正确的结论:每一次我的方块要移动都是和棋子进行交换,所以如果我棋子要向某一个方向走一步,只要我的空格先转移到那儿,然后再我的棋子再和空格交换位置就可以了。

于是乎,我每一次只用先把我的空格从最开始的位置移动到我棋子的相邻四个格子中可行的格子,然后开始用空格不断的搭桥跑SPFA到我的目标点。
分:
预处理
我们在预处理的时候要暂时的标记中心棋子【i】【j】所在的方块是不能动的,因为一旦中心棋子移动了,那么当我空格移动到H方向的时候中心方块不在【i】【j】位置了,这样我的空格移动过去就是没有意义的。

for(int i = 1;i <= N;i++)               //预处理 
    for(int j = 1;j <= M;j++){

      bool v = Map[i][j];
      Map[i][j] = false;

          for(int k = 0;k < 4;k++)          //空格在K方向上 
            for(int h = 0;h < 4;h++)            //方块要去H方向 
                step[i][j][k][h] = bfs(i + dx[k],j + dy[k],i + dx[h],j + dy[h]);

        Map[i][j] = v;

      } 

然后讲怎么计算STEP[i][j][k][h],我采用BFS计算是因为,我这儿找的是最短路,因为这个是在方格图上找最短路那么我一层一层向外搜索,第一次搜索到【tx】【ty】一定就是K方向的空格移动到H方向的最短距离了。(这儿不懂可以画个图自己研究一下,其实DINIC第一遍BFS也是这个道理可以类比理解一下)

inline int bfs(int sx,int sy,int tx,int ty){

    while(!q.empty()) q.pop();

    if(Map[sx][sy] == false || !check(sx,sy)) return INF;
    if(Map[tx][ty] == false || !check(tx,ty)) return INF;
    if(sx == tx && sy == ty) return 0;

    for(int i = 1;i <= N;i++)
      for(int j = 1;j <= M;j++)
        dist[i][j] = INF;

    dist[sx][sy] = 0;
    P = (node){sx,sy};

    q.push(P);

    while(!q.empty()){
        node u = q.front();q.pop();

        if(dist[tx][ty] != INF) return dist[tx][ty];

        for(int i = 0;i < 4;i++)
            if( check(u.x + dx[i],u.y + dy[i]) && dist[ u.x + dx[i] ][ u.y + dy[i] ] > dist[u.x][u.y] + 1 ){

                dist[ u.x + dx[i] ][ u.y + dy[i] ] = dist[u.x][u.y] + 1;
                P = (node){u.x + dx[i],u.y + dy[i]};

                q.push(P);
                if(dist[tx][ty] != INF) return dist[tx][ty];
        }
    }   


    return dist[tx][ty];
}

然后我们就将STEP数组处理完了。我们这个时候对原图能够处理的已经处理完了,我们要开始处理询问了。

我在这儿新定义了一个数组d【i】【j】【k】用来代表,我将中心棋子从起点移动到【i】【j】然后再将空格从原位置移动至【k】方向所需要的总步骤(注意只是将空格移动至k方向但是我的中心棋子还是没有动)
在SPFA开始之前,我要先计算出空格移动至我中心棋子的相邻四个格子所需要的步骤。
然后我将中心格子在[i][j],空白格子在相邻的k方向,这个状态作为起点开始跑SPFA。

bool v = Map[si][sj];Map[si][sj] = false;

    for(int k = 0;k < 4;k++)
        if(Map[si + dx[k]][sj + dy[k]]) d[si][sj][k] = bfs(ei,ej,si + dx[k],sj + dy[k]);


    Map[si][sj] = v;

    return spfa(si,sj,ti,tj);

然后我的核心程序SPFA就要开始了。
然后在此特殊说明一下我的dx【】,和dy【】是经过特殊安排的;
经过我这么安排了之后k和k^1就是向上和向下,或者是向左和向右这种对应关系了。

dx[5] = {-1,1,0,0},
dy[5] = {0,0,-1,1};
//UP DOWN LEFT RIGHT

然后我再对我SPFA中的状态转移作一个说明:
这个d[xv][yv][k^1]就是d[x][y][k0] + step[x][y][k0][k] 的下一个状态。因为d[x][y][k0]就是中心棋子在[x][y],空格在k0方向,这个时候我将k0方向的空格移动至k方向,这个时候+1就代表将中心棋子和K方向上的空格交换位置。这样我的状态就变成了我中心棋子到了d[xv][yv],我的空格到了k的反方向k^1.所以这个时候我的状态就是d[xv][yv][k^1]。

int xv = x + dx[k],yv = y + dy[k];
 if(d[xv][yv][k^1] > d[x][y][k0] + step[x][y][k0][k] + 1)
    d[xv][yv][k^1] = d[x][y][k0] + step[x][y][k0][k] + 1;
inline int spfa(int sx,int sy,int tx,int ty){
    while(!q.empty()) q.pop();

    for(int k = 0;k < 4;k++)
        if(check(sx + dx[k],sy + dy[k]))
            q.push(P = (node){sx,sy,k} )
                ,inq[sx][sy][k] = true;

    while(!q.empty()){
        int x = q.front().x,y = q.front().y,k0 = q.front().k;
        q.pop();inq[x][y][k0] = false;

        for(int k = 0;k < 4;k++){
            int xv = x + dx[k],yv = y + dy[k];
            if(!check(xv,yv)) continue;

  if(d[xv][yv][k^1] > d[x][y][k0] + step[x][y][k0][k] + 1){
     d[xv][yv][k^1] = d[x][y][k0] + step[x][y][k0][k] + 1;
               if(!inq[xv][yv][k^1]) q.push(P = (node){xv,yv,k^1}),inq[xv][yv][k^1] = true;
            }

        }

    }
    int ans = INF;

    for(int i = 0;i < 4;i++)
        ans = min(ans,d[ti][tj][i]);

    return ans != INF ? ans : -1;
}

然后在程序结束的时候就是循环,我的中心棋子在终点,然后空格在我中心棋子的四个方向中间的最小值。
总:

#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;

const int MAXN = 35,dx[5] = {-1,1,0,0},dy[5] = {0,0,-1,1},INF = 0x3f3f3f3f;//UP DOWN LEFT RIGHT

int N,M,Q,T,ei,ej,si,sj,ti,tj;
int step[MAXN][MAXN][5][5],dist[MAXN][MAXN],d[MAXN][MAXN][5];
bool Map[MAXN][MAXN],inq[MAXN][MAXN][5]; 

struct node{
    friend bool operator == (const node &a,const node &b){
        if(a.x == b.x && a.y == b.y && a.k == b.k) return true;
        return false;
    }

    int x,y,k;
}P;

queue<node>q;

inline bool check(int x,int y){ return (1 <= x && x <= N && 1 <= y && y <= M && Map[x][y] );} //判断是否在方格内部 

inline int bfs(int sx,int sy,int tx,int ty){

    while(!q.empty()) q.pop();

    if(Map[sx][sy] == false || !check(sx,sy)) return INF;
    if(Map[tx][ty] == false || !check(tx,ty)) return INF;
    if(sx == tx && sy == ty) return 0;

    for(int i = 1;i <= N;i++)
      for(int j = 1;j <= M;j++)
        dist[i][j] = INF;

    dist[sx][sy] = 0;
    P = (node){sx,sy};

    q.push(P);

    while(!q.empty()){
        node u = q.front();q.pop();

        if(dist[tx][ty] != INF) return dist[tx][ty];

        for(int i = 0;i < 4;i++)
            if( check(u.x + dx[i],u.y + dy[i]) && dist[ u.x + dx[i] ][ u.y + dy[i] ] > dist[u.x][u.y] + 1 ){

                dist[ u.x + dx[i] ][ u.y + dy[i] ] = dist[u.x][u.y] + 1;
                P = (node){u.x + dx[i],u.y + dy[i]};

                q.push(P);
                if(dist[tx][ty] != INF) return dist[tx][ty];
        }
    }   


    return dist[tx][ty];
}

inline void init()
{
    scanf("%d %d %d",&N,&M,&Q);

    for(int i = 1;i <= N;i++)
        for(int j = 1;j <= M;j++)
            scanf("%d ",&Map[i][j]);        

    for(int i = 1;i <= N;i++)               //预处理 
        for(int j = 1;j <= M;j++){

        bool v = Map[i][j];
        Map[i][j] = false;

            for(int k = 0;k < 4;k++)            //空格在K方向上 
              for(int h = 0;h < 4;h++)          //方块要去H方向 
                step[i][j][k][h] = bfs(i + dx[k],j + dy[k],i + dx[h],j + dy[h]);

        Map[i][j] = v;

      } 
}

inline int spfa(int sx,int sy,int tx,int ty){
    while(!q.empty()) q.pop();

    for(int k = 0;k < 4;k++)
        if(check(sx + dx[k],sy + dy[k]))
            q.push(P = (node){sx,sy,k} )
                ,inq[sx][sy][k] = true;

    while(!q.empty()){
        int x = q.front().x,y = q.front().y,k0 = q.front().k;
        q.pop();inq[x][y][k0] = false;

        for(int k = 0;k < 4;k++){
            int xv = x + dx[k],yv = y + dy[k];
            if(!check(xv,yv)) continue;

            if(d[xv][yv][k^1] > d[x][y][k0] + step[x][y][k0][k] + 1){
               d[xv][yv][k^1] = d[x][y][k0] + step[x][y][k0][k] + 1;
               if(!inq[xv][yv][k^1]) q.push(P = (node){xv,yv,k^1}),inq[xv][yv][k^1] = true;
            }

        }

    }
    int ans = INF;

    for(int i = 0;i < 4;i++)
        ans = min(ans,d[ti][tj][i]);

    return ans != INF ? ans : -1;
}

inline int work()
{
    scanf(" %d %d %d %d %d %d ",&ei,&ej,&si,&sj,&ti,&tj);

    if(si == ti && sj == tj) return 0;

    if( !check(si,sj) || !check(ti,tj) ) return -1;

    for(int i = 1;i <= N;i++)
      for(int j = 1;j <= M;j++)
       for(int k = 0;k < 4;k++)
        d[i][j][k] = INF;

    bool v = Map[si][sj];Map[si][sj] = false;

    for(int k = 0;k < 4;k++)
        if(Map[si + dx[k]][sj + dy[k]]) d[si][sj][k] = bfs(ei,ej,si + dx[k],sj + dy[k]);


    Map[si][sj] = v;

    return spfa(si,sj,ti,tj);
}

int main()
{
    init();

    while(Q--)
        printf("%d\n",work());

    return 0;
} 

好累,睡觉去了。。。。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值