【AcWing】1131. 拯救大兵瑞恩

拯救大兵瑞恩

题目

https://www.acwing.com/problem/content/1133/
又是一道有意思的图论题,且码量较大



输入:

4 4 9
9
1 2 1 3 2
1 2 2 2 0
2 1 2 2 0
2 1 3 1 0 
2 3 3 3 0
2 4 3 4 1
3 2 3 3 0
3 3 4 3 0
4 3 4 4 0
2
2 1 2 
4 2 1

输出:

14

思路

拆点

状态表示:\(d(x, y, state)\):所有从起点走到\((x, y)\),且当前有钥匙是\(state\)(状压二进制数)的集合

状态计算:

  1. \((x, y)\)这里有一些钥匙,直接拿去\(state\,|\,key:\)\(d[x, y, state\,|\,key]= min (d[x, y, state\,|\,key], d[x, y, state])\)
  2. 向上下左右四个方向走(没有门和墙;有门且有对应钥匙)\(d[xx, yy, state] = min (d[xx, yy, state], d[x, y, state] + 1)\)

由于该图不具有拓扑序,所以不能用DP来做,要转化为最短路问题:

  1. 对应边权为 0(捡钥匙不花时间):\(d[x,y,state]\rightarrow d[x,y,state\,|\, key]\)
  2. 对应边权为 1 :\(d[x, y, state]\rightarrow d[xx, yy, state]\)

算法:双端队列BFS / dijkstra / SPFA

巧妙处理输入输出:

  1. 二维转化成一维
  2. 创建空地:map存门墙,剩下的就是空地

Code

#include <iostream>
#include <cstring>
#include <queue>
#include <set>

using namespace std;
typedef pair<int, int>pii;
const int N = 11, M = 360, P = 1 << 10;

int n, m, k, p;
int h[N * N], e[M], w[M], ne[M], idx;
int g[N][N], key[N * N];  //存点  存钥匙
int dis[N * N][P];
bool vis[N * N][P];
set <pii> edges;  //存存在阻隔的格子(即门或墙)

int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0};

bool Range (int x, int y) {
    if (x <= 0 || x > n || y <= 0 || y > m)  //悲,又是范围打错
        return false;
    return true;
}

void add (int a, int b, int c) {
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}

//把剩下的没有障碍的边建上,把图补全
void build () {
    for (int x = 1; x <= n; x ++)
        for (int y = 1; y <= m; y ++)
            for (int i = 0; i < 4; i ++) {
                int xx = x + dx[i], yy = y + dy[i];
                if (!Range (xx, yy))
                    continue;
                int a = g[x][y], b = g[xx][yy];
                if (!edges.count ({a, b}))  //没出现过
                    add (a, b, 0);  //无障碍边
            }
}

//双端队列广搜
int bfs () {
    memset (dis, 0x3f, sizeof dis);
    dis[1][0] = 0;
    deque<pii>q;
    q.push_back ({1, 0});

    while (!q.empty ()) {
        auto t = q.front ();
        q.pop_front ();
        int ver = t.first, st = t.second;  //钥匙编号,状态

        if (vis[ver][st])    continue;
        vis[ver][st] = true;

        //找到答案
        if (ver == n * m)
            return dis[ver][st];  //按照递增的顺序找的,所以第一次搜到的就是最短路

        //有钥匙,对应情况1
        if (key[ver]) {
            int state = st | key[ver];   //捡起了钥匙,更新状态
            if (dis[ver][state] > dis[ver][st]) {
                dis[ver][state] = dis[ver][st];  //注意捡钥匙不花时间
                q.push_front ({ver, state});
            }
        }

        //普通边,对应情况2
        for (int i = h[ver]; i != -1; i = ne[i]) {
            int j = e[i];
            if (w[i] && !(st >> w[i] - 1 & 1))
                continue; //有门且无对应钥匙,不通过
            if (dis[j][st] > dis[ver][st] + 1) {
                dis[j][st] = dis[ver][st] + 1;
                q.push_back ({j, st});
            }
        }
    }
    return -1;  //未找到
}

int main () {
    memset (h, -1, sizeof h);
    cin >> n >> m >> p >> k;

    for (int i = 1, t = 1; i <= n; i ++)
        for (int j = 1; j <= m; j ++)
            g[i][j] = t ++;

    while (k --) {
        int x1, y1, x2, y2, type;
        cin >> x1 >> y1 >> x2 >> y2 >> type;
        int a = g[x1][y1], b = g[x2][y2];

        edges.insert ({a, b}), edges.insert ({b, a});  //阻隔是双向的
        if (type)
            add (a, b, type), add (b, a, type);  //建门
    }

    build ();  //完善图

    //存入钥匙
    int s;
    cin >> s;
    while (s --) {
        int x, y, id;
        cin >> x >> y >> id;
        key[g[x][y]] |= 1 << id - 1; //状压存钥匙 -1是为了节省空间,使下标从0开始
    }

    cout << bfs () << endl;
}
posted @ 2022-04-19 21:28  Sakana~  阅读(42)  评论(0编辑  收藏  举报