[题解] PowerOJ 1749 孤岛营救问题 (最短路)
- 传送门 -
https://www.oj.swust.edu.cn/problem/show/1749
Time Limit: 1000 MS Memory Limit: 65536 KB
Total Submit: 50 Accepted: 29 Page View: 566
Description
大兵瑞恩。瑞恩被关押在一个迷宫里,迷宫地形复杂,但幸好麦克得到了迷宫的 地形图。迷宫的外形是一个长方形,其南北方向被划分为N 行,东西方向被划分为M列, 于是整个迷宫被划分为N×M 个单元。每一个单元的位置可用一个有序数对(单元的行号, 单元的列号)来表示。南北或东西方向相邻的2 个单元之间可能互通,也可能有一扇锁着的 门,或者是一堵不可逾越的墙。迷宫中有一些单元存放着钥匙,并且所有的门被分成P类, 打开同一类的门的钥匙相同,不同类门的钥匙不同。 大兵瑞恩被关押在迷宫的东南角,即(N,M)单元里,并已经昏迷。迷宫只有一个入口, 在西北角。也就是说,麦克可以直接进入(1,1)单元。另外,麦克从一个单元移动到另一个 相邻单元的时间为1,拿取所在单元的钥匙的时间以及用钥匙开门的时间可忽略不计。 编程任务: 试设计一个算法,帮助麦克以最快的方式到达瑞恩所在单元,营救大兵瑞恩。
Input
由文件input.txt提供输入数据。第1行有3个整数,分别表示N,M,P的值。第2 行是1 个整数K,表示迷宫中门和墙的总数。第I+2 行(1<=I<=K),有5 个整数,依次为 Xi1,Yi1,Xi2,Yi2,Gi: 当Gi>=1时,表示(Xi1,Yi1)单元与(Xi2,Yi2)单元之间有一扇第Gi类的门,当Gi=0 时, 表示(Xi1,Yi1)单元与(Xi2,Yi2)单元之间有一堵不可逾越的墙(其中,|Xi1-Xi2|+|Yi1-Yi2|=1, 0<=Gi<=P)。 第K+3行是一个整数S,表示迷宫中存放的钥匙总数。 第K+3+J 行(1<=J<=S),有3个整数,依次为Xi1,Yi1,Qi:表示第J 把钥匙存放在(Xi1,Yi1) 单元里,并且第J 把钥匙是用来开启第Qi类门的。(其中1<=Qi<=P)。 输入数据中同一行各相邻整数之间用一个空格分隔。
Output
程序运行结束时,将麦克营救到大兵瑞恩的最短时间的值输出到文件output.txt 中。如 果问题无解,则输出-1。
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
Source
- 题意 -
一个 n*m 的格点图.
俩格点间可能无法通过, 可能需要特定钥匙.
一些格点有一些钥匙.
可以上下左右走一格, 时间为 1.
求最短时间从\((1,1)\)走到\((n,m)\).
- 思路 -
分层建模.
以每一把钥匙的有无来分层(二进制表示).
在每一个状态下, 枚举每一个节点看是否能到达它四周的节点,然后连边, \(0\) 状态下(没有一把钥匙)的\((1,1)\)节点是起点, 任一状态下的\((n, m)\)都是终点.
跑最短路.
细节见代码.
PS:
一个节点可能有多把钥匙.
- 代码 -
#include<cstdio>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 2050 * 100;
const int M = N * 5;
const int inf = 0x3f3f3f3f;
int TO[M], V[M], NXT[M];
int DIS[N], VIS[N], HD[N];
int MAP[105][105], KY[105][105];
int X[4] = {-1, 1, 0, 0}, Y[4] = {0, 0, -1, 1};
int n, m, ss, tt, p, k, s, tot, sz;
queue<int> q;
int mk(int x, int y) {
return (x - 1) * m + y;
}
int mk1(int p, int z) {
return z * tot + p;
}
bool ok(int z, int k) {
if (k == -1)
return true;
if (z & (1 << k))
return true;
return false;
}
int spfa() {
memset(DIS, 0x3f, sizeof (DIS));
DIS[ss] = 0;
q.push(ss);
while (!q.empty()) {
int u = q.front();
q.pop();
VIS[u] = 0;
for (int i = HD[u]; i != -1; i = NXT[i]) {
int v = TO[i];
if (DIS[v] > DIS[u] + V[i]) {
DIS[v] = DIS[u] + V[i];
if (!VIS[v]) {
VIS[v] = 1;
q.push(v);
}
}
}
}
if (DIS[tt] == inf) return -1;
return DIS[tt];
}
void add(int x, int y, int z) {
TO[sz] = y; V[sz] = z;
NXT[sz] = HD[x]; HD[x] = sz++;
}
int main() {
memset(MAP, -1, sizeof (MAP));
memset(HD, -1, sizeof (HD));
scanf("%d%d%d%d", &n, &m, &p, &k);
int x1, x2, y1, y2, tmp;
tot = n * m;
for (int i = 1; i <= k; ++i) {
scanf("%d%d%d%d%d", &x1, &y1, &x2, &y2, &tmp);
int k1 = mk(x1, y1), k2 = mk(x2, y2);
MAP[k1][k2] = MAP[k2][k1] = tmp - 1;
if (tmp == 0)
MAP[k1][k2] = MAP[k2][k1] = -2;
}
scanf("%d", &s);
for (int i = 1; i <= s; ++i) {
scanf("%d%d%d", &x1, &y1, &tmp);
KY[x1][y1] |= 1 << (tmp - 1); //一个节点可能不止一把钥匙
}
tt = 204805;
for (int i = 0; i < (1 << p); ++i) {
for (int x = 1; x <= n; ++x) {
for (int y = 1; y <= m; ++y) {
if (x == n && y == m)
add(mk1(mk(x, y), i), tt, 0); //设一个虚点做终点, 也可以最后枚举这些点.
int pt = mk(x, y);
for (int l = 0; l < 4; ++l) {
int z = i, xx = x + X[l], yy = y + Y[l];
if (xx > 0 && yy > 0 && xx <= n && yy <= m);
else continue;
if (MAP[mk(x, y)][mk(xx, yy)] == -2 || !ok(i, MAP[mk(x, y)][mk(xx, yy)])) //有墙或者没钥匙
continue;
if (KY[xx][yy])
z |= KY[xx][yy];
add(mk1(pt, i), mk1(mk(xx, yy), z), 1);
}
}
}
}
ss = mk1(mk(1, 1), 0);
printf("%d\n", spfa());
return 0;
}