【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\)(状压二进制数)的集合
状态计算:
- \((x, y)\)这里有一些钥匙,直接拿去\(state\,|\,key:\)\(d[x, y, state\,|\,key]= min (d[x, y, state\,|\,key], d[x, y, state])\)
- 向上下左右四个方向走(没有门和墙;有门且有对应钥匙)\(d[xx, yy, state] = min (d[xx, yy, state], d[x, y, state] + 1)\)
由于该图不具有拓扑序,所以不能用DP来做,要转化为最短路问题:
- 对应边权为 0(捡钥匙不花时间):\(d[x,y,state]\rightarrow d[x,y,state\,|\, key]\)
- 对应边权为 1 :\(d[x, y, state]\rightarrow d[xx, yy, state]\)
算法:双端队列BFS / dijkstra / SPFA
巧妙处理输入输出:
- 二维转化成一维
- 创建空地: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;
}