• luogu P1979 华容道


    P1979 华容道

    题目描述

    【问题描述】

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

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

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

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

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

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

    给定一个棋盘,游戏可以玩 q 次,当然,每次棋盘上固定的格子是不会变的, 但是棋盘上空白的格子的初始位置、 指定的可移动的棋子的初始位置和目标位置却可能不同。第 i 次

    玩的时候, 空白的格子在第 EXi 行第 EYi 列,指定的可移动棋子的初始位置为第 SXi 行第 SYi列,目标位置为第 TXi 行第 TYi 列。

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

    输入输出格式

    输入格式:

    输入文件为 puzzle.in。

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

    接下来的 n 行描述一个 n*m 的棋盘,每行有 m 个整数,每两个整数之间用一个空格隔开,每个整数描述棋盘上一个格子的状态,0 表示该格子上的棋子是固定的,1 表示该格子上的棋子可以移动或者该格子是空白的。接下来的 q 行,每行包含 6 个整数依次是 EXi、EYi、SXi、SYi、TXi、TYi,每两个整数之间用一个空格隔开,表示每次游戏空白格子的位置,指定棋子的初始位置和目标位置。

    输出格式:

    输出文件名为 puzzle.out。

    输出有 q 行,每行包含 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

    说明

    【输入输出样例说明】

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

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

    移动过程如下:

    1. 第二次游戏,空白格子的初始位置是(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。

    思路:

    考虑一下怎么做这题:

      ①灵活改变状态表示方法,整合无用状态,减少无用状态

      ②将状态抽离,构图,转化为图论问题

    故:正确的步骤为

    第一步,抽离有用状态

    第二步,有用状态与有用的后继状态 连边构图,

        边权为:由有用状态 到 后继状态 所需的最小步数

    第三步,初始状态 到 目标状态 ,跑最短路(最好是跑spfa)

                所以正解就出来啦~~即bfs+spfa

    技巧:

    我们可以用0,1,2,3分别表示空白格在指定格的上右下左

    状态=((行号-1)*列数+(列号-1))*4+ 0/1/2/3

    int numbers(int i,int j) { return (i-1)*m+j-1<<2; } 状态=number(x,y)+0/1/2/3

    其实所有网格图中的状态都可以采取类似的方法

    状态编号= 图中编号* S+ x ,x∈[0,S) S=每种编号状态数

    所以在定义dx数组跟dy数组的时候一定要一一对应!!!

    坑点:

    ①如上技巧所述:

      在定义dx数组跟dy数组的时候一定要一一对应!!!不然很吃亏...

    ②写n,m千万不要写反了...写反了很尴尬,读不进去...虽然是可以检查出来,不过这个真的很致命!!!!

    ③在进行连边的时候,其实还有第四种后继状态:

      就是空白格与指定格的位置互换

      我使用的是上右下左这样的dx,dy,所以第四种状态的转换就简单的变为{空白格的numbers+(d+2)%4}    ps:d是枚举的初始状态空白格相对于指定格的方向。

      除此之外的状态用指定格的numbers+d即可

    代码:

    因为我细心的敲了好多注释,快夸我快夸我=w=

    #include <iostream>
    #include <cstring>
    #include <cstdio>
    #include <queue>
    #define INF 0x7fffffff
    using namespace std;
    
    const int Maxt = 32;
    const int Maxn = 3610;
    const int Maxm = Maxn * 5;
    int n,m,p;
    bool a[Maxt][Maxt],vis[Maxn];
    int dx[4] = {-1,0,1, 0},
        dy[4] = { 0,1,0,-1};
    int predis[Maxt][Maxt],dis[Maxn];
    
    struct game {
        int next,to,w;
    }e[Maxm];
    int top,head[Maxn];
    void add(int u,int v,int val) {
        top++;
        e[top].to=v;
        e[top].w=val;
        e[top].next=head[u];
        head[u]=top;
    }
    
    struct start {
        int x,y;
    } nxt,cur;
    queue<start>q;
    queue<int>que;
    
    int numbers(int i,int j) {
        /*
        因为固定一个指定块之后的状态对应着5种状态,
        都是不同的状态,所以要开5倍,即必须编号多编 
        */
        j--; ///从0开始编号 
        return (i-1)*m+j<<2;
    //  return (i-1)*m+j<<2;
        ///如果像这样从1开始编号的话,我们的存图的struct game e数组就需要多开5个空间!!!所以还是最好从0编号好 
    }
    
    void bfs(int ex,int ey,int px,int py,int d) {
        int cx,cy,nx,ny; 
        memset(predis,-1,sizeof(predis));
        /*
            predis数组表示啥呢??? 
            其实简单来说就是:
            固定指定块,空白块在指定块的其中一个方向
            然后再由这个状态到其他状态的最短路
            没错!储存的就是最短路的距离!!! 
            其他状态是指:
            固定了指定块之后,空白块在指定块的另外三个方向
        */
        ///指定格  
        predis[px][py]=1;
        ///空白格
        predis[ex][ey]=0;
        ///bfs当前空白格移动到每个点所需要的步数 
        cur.x=ex,cur.y=ey;
        q.push(cur);
        while(!q.empty()) {
            cur=q.front();
            q.pop();
            cx=cur.x,cy=cur.y;
            for(int i=0; i<4; ++i) {
                nx=cur.x+dx[i],ny=cur.y+dy[i];
                if(a[nx][ny] && predis[nx][ny]==-1) {
                    predis[nx][ny]=predis[cx][cy]+1;
                    nxt.x=nx,nxt.y=ny;
                    q.push(nxt);
                }
            }
        }
        ///如刚才所述 
        if(d==8) return;
        int tmp=numbers(px,py);
        for(int i=0; i<4; ++i) {
            int x=px+dx[i],y=py+dy[i];
            if(predis[x][y]>0)
            /*
            因为在之前已经把predis[ex][ey]赋值为0,
            所以这个条件就是相当于当前枚举的这个指定块与空白块的状态,
            与它的后继状态进行连边 
            */
                /*----------这里重要----------*/ 
                add(tmp+d,tmp+i,predis[x][y]);
                /*----------真的重要----------*/
        }
        /*
        最后一种后继状态是指定格与空白格交换位置
        因为规定的0123是上右下左,所以(d+2)%4可以直接转化为对面的方向
        交换相当于走一步,所以边权为1 
        */
        add(tmp+d,numbers(ex,ey)+(d+2)%4,1);
    }
    
    void spfa(int sx,int sy) {
        int tmp;
        memset(dis,-1,sizeof(dis));
        for(int i=0; i<4; ++i) {
            int x=sx+dx[i],y=sy+dy[i];
            if(predis[x][y]!=-1) {
                tmp=numbers(sx,sy)+i;
                dis[tmp]=predis[x][y];
                que.push(tmp);
            }
        }
        int u;
        while(!que.empty()) {
            u=que.front();
            que.pop();
            vis[u]=false;
            for(int i=head[u]; i; i=e[i].next) {
                int v=e[i].to;
                if(dis[v]==-1 || dis[v]>dis[u]+e[i].w) {
                    dis[v]=dis[u]+e[i].w;
                    if(!vis[v]) {
                        vis[v]=true;
                        que.push(v);
                    }
                }
            }
        }
    } 
    
    int main() {
        scanf("%d%d%d",&n,&m,&p);
        ///枚举并固定指定格 
        for(int i=1; i<=n; ++i)
            for(int j=1; j<=m; ++j)
                scanf("%d",&a[i][j]);
        for(int i=1; i<=n; ++i)
            for(int j=1; j<=m; ++j)
                if(a[i][j]) {
                    ///枚举固定空白格,上,右,下,左(顺时针旋转,方便进行转换) 
                    ///bfs在固定指定格与空白格的5种状态 
                    if(a[i-1][j]) bfs(i-1,j,i,j,0); 
                    if(a[i][j+1]) bfs(i,j+1,i,j,1);
                    if(a[i+1][j]) bfs(i+1,j,i,j,2);
                    if(a[i][j-1]) bfs(i,j-1,i,j,3);
                }
        /*
        空白格:ex,ey 
        指定格的初始位置:sx,sy 
        目标位置:tx,ty 
        */
        int ex,ey,sx,sy,tx,ty,ans;
        while(p--) {
            scanf("%d%d%d%d%d%d",&ex,&ey,&sx,&sy,&tx,&ty);
            if(sx==tx && sy==ty) {
                printf("0
    ");
                continue;
            }
            /*-----------最后一次bfs-----------*/
            /*
            因为我们是直接枚举空白快与指定块连接在一起的状态,
            但是他们其实有可能初始状态并没有连接,
            所以强行将她们进行连接,
            但是不在图中进行连边,
            所以随便弄个d,如果bfs之后要建边了,
            若是胡乱搞的这个d,直接return即可 
            */ 
            bfs(ex,ey,sx,sy,8);
            ///普♂通♀的spfa,跑最短路 
            spfa(sx,sy);
            ans=INF;
            ///将目标位置进行编号 
            int tmp=numbers(tx,ty);
            for(int i=0; i<4; ++i)
                ///+i是空白块的状态方向
                if(dis[tmp+i]!=-1) 
                    ///往小里面更新 
                    ans=min(ans,dis[tmp+i]);
            if(ans==INF)
                ans=-1;
            printf("%d
    ",ans); 
        }
        return 0;
    }

    如果运气好也是错,那我倒愿意错上加错!

    ❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀

  • 相关阅读:
    2021123内部群每日三题清辉PMP
    20211125内部群每日三题清辉PMP
    20211222内部群每日三题清辉PMP
    202215内部群每日三题清辉PMP
    2022117内部群每日三题清辉PMP
    2021126内部群每日三题清辉PMP
    20211126内部群每日三题清辉PMP
    2021122内部群每日三题清辉PMP
    202216内部群每日三题清辉PMP
    20211228内部群每日三题清辉PMP
  • 原文地址:https://www.cnblogs.com/zxqxwnngztxx/p/7348987.html
Copyright © 2020-2023  润新知