华容道

Posted 夕時弥月風

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了华容道相关的知识,希望对你有一定的参考价值。

先扔个代码了……之后再写详细点吧
感谢@Erutsiom 巨佬的解读题目
dalao题解链接



//by Saber Alter Official
//Erishikigal & Ishtar
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <queue>

//#define Kama

typedef long long ll;
typedef unsigned long long ull;

const int N = 35, inf = 0x3f3f3f3f;

int n, m, q, ex, ey, sx, sy, tx, ty;
bool G[N][N], OK[N][N][5];//OK[i][j][k]表示绿色格子在(i,j)位置的k方向是否能有空白格子 
int mv[4][2] = {{-1, 0},{1, 0},{0, -1},{0, 1}};//0上 1下 2左 3右 
/*建边操作*/
struct node {
    int to, next, val;
}edge[5015];
int head[5015], cnt;
inline void add_edge(int u, int v, int val) {
    edge[++cnt].to = v;
    edge[cnt].next = head[u];
    edge[cnt].val = val;
    head[u] = cnt;
}
/*建边结束*/
/*合法性检验*/
inline bool Check(int x, int y) {
    if(x <= 0 || y <= 0 || x > n || y > m || !G[x][y]) return false;
    return true;
}
/*编号函数第一次看比较难理解
举个例子,我们让(1,1)格子四个状态为0123
(1,2)格子四个状态是4567, 以此类推
就可以得到一个横行里相同状态比上一列多4。 
再假设一共五列,(2,1)就是20.21.22.23,比上一行相同列相同状态多了4*m个
所以可以推出x上的运算应该是要乘以m,为了让第一行出现0123,要先把x变成(x-1) 
然后加上y,最外面乘4,就可以得到上面的倍数关系,最后加上dir-4就可以了 
*/
inline int SetNum(int x, int y, int dir) {
    return (((x - 1) * m + y) * 4 + (dir - 4));
}

struct Vacancy {
    int x, y, step;
};
std::queue<Vacancy> Que;
inline void Clear(std::queue<Vacancy> &Que) {
    std::queue<Vacancy> Empty;
    std::swap(Que, Empty);
}
//不能通过fbdx,fbdy从stx,sty到tarx, tary 
inline int BFS(int fbdx, int fbdy, int stx, int sty, int tarx, int tary) {
    //初始化 
    Clear(Que);
    bool vis[N][N];
    memset(vis, 0, sizeof vis);
    //初始化队头 
    Vacancy head;
    head.x = stx;
    head.y = sty;
    head.step = 0;//起步是0哦不要打成1 
    Que.push(head);
    
    while(!Que.empty()) {
        Vacancy pos = Que.front();
        Que.pop();
        if(pos.x == tarx && pos.y == tary)  {
            return pos.step;
        }
        
        if(vis[pos.x][pos.y]) continue;
        vis[pos.x][pos.y] = true;
        
        for(int k = 0;k < 4;k++) {
            Vacancy New;
            New.x = pos.x + mv[k][0];
            New.y = pos.y + mv[k][1];
            if(Check(New.x, New.y)) {//新位置本身合法 
                if(vis[New.x][New.y]) continue;//不符合两种条件 
                if(New.x == fbdx && New.y == fbdy) continue;
                
                New.step = pos.step + 1;
                Que.push(New);
            }
        }   
    }
    return inf;//队列跑空了也找不到 
}

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", &G[i][j]);
        }
    }
    
    #ifdef Kama
    for(int i = 1;i <= n;i++) {
        for(int j = 1;j <= m;j++) {
            printf("%d%c", G[i][j], j == m ? '\n' : ' ');
        }
    }
    #endif
    
    for(int i = 1;i <= n;i++) {
        for(int j = 1;j <= m;j++) {
            if(!G[i][j]) continue;//本身就不能走动 
            for(int k = 0;k < 4;k++) {
                if(Check(i + mv[k][0], j + mv[k][1]))//绿格子和空白格子的移动处理 
                    OK[i][j][k] = true;//预处理移动,看这个位置向哪里移动合法 
            }
        }
    }
    //空格绕着绿色格子乱动的距离 
    for(int i = 1;i <= n;i++) {
        for(int j = 1;j <= m;j++) {//i,j枚举绿色格子位置 
            for(int k = 0;k < 4;k++) {
                for(int p = k + 1;p < 4;p++) {//空白格在各个位置尝试移动 
                    if(OK[i][j][k] && OK[i][j][p]) {//k=1 p=2.3.4 //k=2 p=3.4//k=3 p=4就枚举了所有情况 
                        int _knum = SetNum(i, j, k);//给格子编号,用来存在图里 
                        int _pnum = SetNum(i, j, p);//一样啦 
                        int _step = BFS(i, j, i + mv[k][0], j + mv[k][1], i + mv[p][0], j + mv[p][1]);
                        if(_step == inf) continue;//不可能到达 
                        add_edge(_knum, _pnum, _step);//建边! 
                        add_edge(_pnum, _knum, _step);
                    }
                }
            }
        }
    }
    //空格和绿色格子打包移动,也就是不断交换位置来移动 
    //这是左右来回推进
    for(int i = 1;i <= n;i++) {
        for(int j = 1;j < m;j++) {
            if(OK[i][j][3] && OK[i][j + 1][2]) {//j在左边,右有空格 
                int _lefNum = SetNum(i, j + 1, 2);//j+1在右边,左有空格 
                int _rigNum = SetNum(i, j, 3);
                add_edge(_lefNum, _rigNum, 1);
                add_edge(_rigNum, _lefNum, 1);
            }
        }
    } 
    //上下,意义同理啦
    for(int i = 1;i < n;i++) {
        for(int j = 1;j <= m;j++) {
            if(OK[i][j][1] && OK[i + 1][j][0]) {//上面的下边有空白格,下面的上面有空白格 
                int _upNum = SetNum(i, j, 1);
                int _dowNum = SetNum(i + 1, j, 0);
                add_edge(_upNum, _dowNum, 1);
                add_edge(_dowNum, _upNum, 1);
            }
        }
    } 
}

int dis[5005];//dis开小了!!!!!!!!!!!!!!!!!!!!!!!!! 
std::queue<int> que;
bool vis[5005];
inline void Solve() {
    memset(dis, 0x3f3f3f3f, sizeof dis);
    memset(vis, 0, sizeof vis);
    
    #ifdef Kama
    for(int i = 1;i <= 100;i++) {
        printf("%d\n", dis[i]);
    }
    #endif
    
    for(int k = 0;k < 4;k++) {
        if(Check(sx + mv[k][0], sy + mv[k][1])) {//枚举空白格到绿色格子周围的情况 
            int nowdis = BFS(sx, sy, ex, ey, sx + mv[k][0], sy + mv[k][1]);
            if(nowdis == inf) continue;
            int nownum = SetNum(sx, sy, k);
            que.push(nownum);
            dis[nownum] = nowdis;
            #ifdef Kama
            printf("dis:%d\n", dis[nownum]);
            #endif
            vis[nownum] = true;
        }
    }
    //上面的循环是针对每次询问,先让空白格跑到绿色格子周围,完成一个初始化
    //接下来,因为我们已经得到的绿色和空白格子在一起的位置编号 
    //又已经建好了这个棋盘上所有绿色和空白格子一起互达的图,就可以跑spfa了!
    while(!que.empty()) {
        int u = que.front();
        que.pop();
        vis[u] = false;
        for(int i = head[u];i;i = edge[i].next) {
            int v = edge[i].to;
            if(dis[v] > dis[u] + edge[i].val) {
                dis[v] = dis[u] + edge[i].val;
                #ifdef Kama
                printf("cdis:%d %d\n", dis[v], dis[u]);
                #endif
                
                if(!vis[v]) {
                    #ifdef Kama
                    printf("v:%d\n", v);
                    #endif
                    que.push(v);
                    vis[v] = true;
                }
            }
        }
    }
    
    
    
    /*最后检查一下答案就可以了*/
    int ans = inf;
    for(int k = 0;k < 4;k++) {
        int tarnum = SetNum(tx, ty, k);
        ans = std::min(ans, dis[tarnum]);
        #ifdef Kama
        printf("tar:%d\n", tarnum);
        printf("dis:%d%c", dis[tarnum], k == 3 ? '\n' : ' ');
        #endif
    }
    
    if(ans == inf) printf("-1\n");
    else {
        printf("%d\n", ans);
    }
}

void Query() {
    for(int i = 1;i <= q;i++) {//空白格信息 绿色格子信息 终点信息 
        scanf("%d %d %d %d %d %d", &ex, &ey, &sx, &sy, &tx, &ty);
        if(sx == tx && sy == ty) {//本身在终点 
            printf("0\n");
            continue;
        }
        else if(!G[sx][sy]) {//起点不合法 
            printf("-1\n");
            continue;
        }
        else if(!G[tx][ty]) {//终点不合法 
            printf("-1\n");
            continue;
        }
        Solve();
    }
}

int main() {
    init();
    
    Query();
    return 0;
}

以上是关于华容道的主要内容,如果未能解决你的问题,请参考以下文章

P1979 华容道

NOIP2013华容道

华容道

[NOIP2013]华容道 又是爆搜

bzoj 2013 华容道

华容道