【题解】P1979 华容道

版权声明:Powered By Fighter https://blog.csdn.net/qq_30115697/article/details/89392369

AK 提高试炼场祭

原题传送门

题解

这是一道神仙题

首先发现爆搜有70分!!!要是在考场上那肯定直接暴力啊!

然而还是得写正解。

首先,爆搜慢是因为有很多无用状态(比如空格在乱跑但指定棋子并没有移动)。所以我们要剪掉这些状态。

我们考虑只搜有用的状态。也就是说我们强制让空格处于棋子的四周,这个时候就会出现两种移动的情况:

  1. 空格绕棋子四周走。
  2. 空格与棋子互换位置。

对于第一种情况,我们发现可以通过bfs求出空格到达下一个位置的时间(前提是不能经过棋子所在的位置)。第二种情况就更简单一些,时间为1。

再说一下状态的组成:状态=当前棋子位置+空格对于棋子的相对方向(表示为 ( x , y , t ) (x,y,t) )。

于是我们发现状态之间是可以通过上面两种方式进行转移,并且如果我们把得到的时间作为权值,并在状态之间连边,就会形成一张无向图!!于是可以愉快地跑最短路!!而且我们发现要求的终点位置是确定的,我们只需要枚举空格位于哪个方向就可以得到终点状态最小值。

还有一些细节问题:

  1. 如何表示状态:我们把棋盘上的每一个位置先顺序标号,再加4个方向编号0~3,表示空格位于当前位置的哪个方向。于是可以得到一个状态 ( x , y , t ) (x,y,t) 的编号为 ( x ( m 1 ) + y 1 ) 4 + t (x*(m-1)+y-1)*4+t
  2. 如何判断固定死的格子:这个很简单,我们预处理 o k [ i ] [ j ] [ k ] ok[i][j][k] 表示状态 ( i , j , k ) (i,j,k) 是否可以存在(先不考虑能否到达,仅判断越界和固定)。然后建图的时候判断即可
  3. 空格不一定在起始点周围:这一部分显然可以暴力求。通过一次bfs,求出空格到达起点四周所需的时间,并把这四个状态扔到spfa的队列中作为初始状态,然后在跑spfa的时候就会自动枚举。
  4. 对于输入的特判:如果起点与终点重合,输出0;如果起点和终点至少有一个被固定,输出-1。最后如果最短路无法到达终点状态,输出-1。

代码

这可能是我写过除了树剖以外码量数一数二的题了

#include <bits/stdc++.h>
#define MAX 5500
#define clear(x) (memset(x, 0, sizeof(x)))
#define INF 0x3f3f3f3f
using namespace std;

const int mx[] = {-1,1,0,0}, my[] = {0,0,-1,1};

int cnt, head[MAX*10], Next[MAX*100], vet[MAX*100], cost[MAX*100];
int n, m, a[55][55];
int sx, sy, tx, ty, ex, ey;

inline bool chk(int x, int y) {     //检查是否能到达(x,y)
    if(x<=0 || x>n || y<=0 || y>m) return false;
    return a[x][y];
}

inline int get(int x, int y, int t) {       //获取状态编号
    return ((x-1)*m+y-1)*4+t;
}

struct node {
    int x, y, step;
};

bool mark[55][55];
int bfs(int sx, int sy, int tx, int ty, int dx, int dy) {   //从(sx,sy)->(tx,ty)且不经过(dx,dy)
    queue<node> q;
    clear(mark);
    mark[sx][sy] = true;
    q.push((node) {
            sx, sy, 0
    });
    while (!q.empty()) {
        node t = q.front();
        q.pop();
        if(t.x == tx && t.y == ty) {
            return t.step;
        }
        for (int i = 0; i < 4; ++i) {
            int u = t.x+mx[i], v = t.y+my[i];
            if(!chk(u, v) || (u==dx && v==dy) || mark[u][v]) continue;
            mark[u][v] = true;
            q.push((node) {
                    u, v, t.step+1
            });
        }
    }
    return INF;
}

bool ok[55][55][5];       //(i,j)四周是否可能存在空格

void add(int x, int y, int w) {
    cnt++;
    Next[cnt] = head[x];
    head[x] = cnt;
    vet[cnt] = y;
    cost[cnt] = w;
}

void init() {
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) {
            if(!a[i][j]) continue;
            for (int k = 0; k < 4; ++k) {
                if(chk(i+mx[k], j+my[k])) {
                    ok[i][j][k] = true;
                }
            }
        }
    }
    //空格绕棋子四周转
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) {
            for (int k = 0; k < 4; ++k) {
                for (int l = k+1; l < 4; ++l) {
                    if(!ok[i][j][k] || !ok[i][j][l]) continue;
                    int x = get(i, j, k), y = get(i, j, l);
                    int w = bfs(i+mx[k], j+my[k], i+mx[l], j+my[l], i, j);
                    if(w == INF) continue;
                    add(x, y, w), add(y, x, w);
                }
            }
        }
    }
    //空格和指定棋子互换
    for (int i = 1; i <= n; ++i) {      //左右互换
        for (int j = 1; j < m; ++j) {
            if(ok[i][j][3] && ok[i][j+1][2]) {
                int x = get(i,j,3), y = get(i,j+1,2);
                add(x, y, 1), add(y, x, 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 x = get(i,j,1), y = get(i+1,j,0);
                add(x, y, 1), add(y, x, 1);
            }
        }
    }
}

int dis[MAX*100], vis[MAX*100];
queue<int> q;
void spfa() {
    memset(dis, 0x3f, sizeof(dis));
    memset(vis, 0, sizeof(vis));
    //先让空格走到指定位置四周
    for (int i = 0; i < 4; ++i) {
        int u = sx+mx[i], v = sy+my[i];
        if(!chk(u, v)) continue;
        int w = bfs(ex, ey, u, v, sx, sy);
        if(w == INF) continue;
        int id = get(sx, sy, i);
        dis[id] = w;
        q.push(id);
        vis[id] = true;
    }
    //spfa
    while(!q.empty()) {
        int t = q.front();
        q.pop();
        vis[t] = false;
        for (int i = head[t]; i; i = Next[i]) {
            int v = vet[i];
            if(dis[v] > dis[t]+cost[i]) {
                dis[v] = dis[t]+cost[i];
                if(!vis[v]) {
                    vis[v] = true;
                    q.push(v);
                }
            }
        }
    }
    int ans = INF;
    for (int i = 0; i < 4; ++i) {
        ans = min(ans, dis[get(tx, ty, i)]);
    }
    if(ans == INF) puts("-1");
    else printf("%d\n", ans);
}

int main() {
    int q;
    cin >> n >> m >> q;
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) {
            scanf("%d", &a[i][j]);
        }
    }
    init();
    while (q--) {
        scanf("%d%d%d%d%d%d", &ex, &ey, &sx, &sy, &tx, &ty);
        if(sx == tx && sy == ty) puts("0");
        else if(!a[tx][ty] || !a[sx][sy]) puts("-1");
        else {
            spfa();
        }
    }

    return 0;
}

总结

这道题的难点在于他别出心裁SB的把状态抽出来建图,这种做法十分的罕见。

文章内有彩蛋哦

猜你喜欢

转载自blog.csdn.net/qq_30115697/article/details/89392369