P4011 孤岛营救问题 - 分层图最短路

新博客地址:https://gyrojeff.top,欢迎访问! 本文为博客自动同步文章,为了更好的阅读体验,建议您移步至我的博客👇

本文标题:P4011 孤岛营救问题 - 分层图最短路

文章作者:gyro永不抽风

发布时间:2020年09月19日 - 19:09

最后更新:2020年09月19日 - 19:09

原始链接:http://hexo.gyrojeff.moe/2020/09/19/P4011-%E5%AD%A4%E5%B2%9B%E8%90%A5%E6%95%91%E9%97%AE%E9%A2%98-%E5%88%86%E5%B1%82%E5%9B%BE%E6%9C%80%E7%9F%AD%E8%B7%AF/

许可协议: 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 转载请保留原文链接及作者!

题目

题目描述

19441944 年,特种兵麦克接到国防部的命令,要求立即赶赴太平洋上的一个孤岛,营救被敌军俘虏的大兵瑞恩。瑞恩被关押在一个迷宫里,迷宫地形复杂,但幸好麦克得到了迷宫的地形图。迷宫的外形是一个长方形,其南北方向被划分为 NN 行,东西方向被划分为 MM 列,于是整个迷宫被划分为 N×MN\times M 个单元。每一个单元的位置可用一个有序数对(单元的行号,单元的列号)来表示。南北或东西方向相邻的 22 个单元之间可能互通,也可能有一扇锁着的门,或者是一堵不可逾越的墙。迷宫中有一些单元存放着钥匙,并且所有的门被分成PP 类,打开同一类的门的钥匙相同,不同类门的钥匙不同。

大兵瑞恩被关押在迷宫的东南角,即 (N,M)(N,M) 单元里,并已经昏迷。迷宫只有一个入口,在西北角。也就是说,麦克可以直接进入 (1,1)(1,1) 单元。另外,麦克从一个单元移动到另一个相邻单元的时间为 11,拿取所在单元的钥匙的时间以及用钥匙开门的时间可忽略不计。

试设计一个算法,帮助麦克以最快的方式到达瑞恩所在单元,营救大兵瑞恩。

输入格式

11 行有 33 个整数,分别表示 N,M,PN,M,P 的值。

22 行是 11 个整数 KK,表示迷宫中门和墙的总数。

I+2I+2(1IK)(1\leq I\leq K),有 55 个整数,依次为Xi1,Yi1,Xi2,Yi2,GiX_{i1},Y_{i1},X_{i2},Y_{i2},G_i

  • Gi1G_i \geq 1 时,表示 (Xi1,Yi1)(X_{i1},Y_{i1}) 单元与 (Xi2,Yi2)(X_{i2},Y_{i2}) 单元之间有一扇第 GiG_i 类的门

  • Gi=0G_i=0 时,表示 (Xi1,Yi1)(X_{i1},Y_{i1}) 单元与 (Xi2,Yi2)(X_{i2},Y_{i2}) 单元之间有一堵不可逾越的墙(其中,Xi1Xi2+Yi1Yi2=1|X_{i1}-X_{i2}|+|Y_{i1}-Y_{i2}|=10GiP0\leq G_i\leq P)。

K+3K+3 行是一个整数 SS,表示迷宫中存放的钥匙总数。

K+3+JK+3+J(1JS)(1\leq J\leq S),有 33 个整数,依次为 Xi1,Yi1,QiX_{i1},Y_{i1},Q_i:表示第 JJ 把钥匙存放在 (Xi1,Yi1)(X_{i1},Y_{i1})单元里,并且第 JJ 把钥匙是用来开启第 QiQ_i 类门的。(其中1QiP1\leq Q_i\leq P)。

输入数据中同一行各相邻整数之间用一个空格分隔。

输出格式

将麦克营救到大兵瑞恩的最短时间的值输出。如果问题无解,则输出 1-1

输入输出样例

输入 #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
输出 #1
14

说明/提示

Xi1Xi2+Yi1Yi2=1,0GiP|X_{i1}-X_{i2}|+|Y_{i1}-Y_{i2}|=1,0\leq G_i\leq P

1QiP1\leq Q_i\leq P

N,M,P10,K<150,S14N,M,P\leq10, K<150,S\leq 14

题解

题目中问要花费的最短时间,那其实就可以联想到最短路问题或者直接BFS。那记下来的问题就在于如何建图。我们可以发现,这里有着$p$把钥匙,要处理好这些钥匙是问题的关键。所以,我们可以建立分层图,而且分层图的层数是当前所有的钥匙的状态压缩。

形象地解释的话:如果现在是$i$层,那么我们先将$i$转换为二进制(假设一共有4种钥匙种类,那么$i$可能是:

$$(1101)_2 $$

意思就是有着第一种、第三种、第四种钥匙的层(从后往前看)。如果无法理解分层图,那么不妨看看前两篇博文,都是分层图的题。

所以接下来,我们只需要关心如何建立图,图建立好之后我们就可以扔给堆优化过的Dijkstra直接算就行了。

我们根据以下几点建图:

  • 如果有不可逾越的墙,不连边
  • 如果输入中没有提到这条边,在每层都连
  • 如果输入中提到了,且需要钥匙,那么先循环,然后按位运算匹配当前层是否拥有开门钥匙。若有则连边
  • 如果这一点有钥匙,那么在这个点,从所有没有钥匙的层,可以转移到有钥匙的层,连边。

最后答案其实就是把每一层的$(N, M)$遍历一遍去一个最小。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#include <iostream>
#include <queue>
#include <cstdio>

using namespace std;

const int maxg = 10;
const int maxk = 155;
const int maxs = 15;
const int maxn = 11;
const int maxm = 11;
int n, m, p, k, s;
int ecnt = 2;
int head[maxn * maxm * (1 << maxg)], dis[maxn * maxm * (1 << maxg)];
bool vis[maxn * maxm * (1 << maxg)];
struct edge { int to, next, w; } g[(1 << maxg) * (maxk * 2 * 4 + maxs)];
void add_edge(int u, int v, int w) { g[ecnt] = (edge) { v, head[u], w }; head[u] = ecnt ++; }
void add_edge(int x1, int y1, int l1, int x2, int y2, int l2, int w) {
add_edge(y1 + (x1 - 1) * m + m * n * l1,
y2 + (x2 - 1) * m + m * n * l2, w);
}
inline int id (int x, int y) {
return (x - 1) * m + y ;
}
int map[200][200];

struct node {
int dis, pos;
friend bool operator < (const node &a, const node &b) { return a.dis > b.dis; }
};
priority_queue<node> q;

void dijkstra() {
for (int i = 1; i <= m * n * (1 << p); i ++) dis[i] = 2e9;
dis[1] = 0;
q.push((node) { dis[1], 1 });
while (!q.empty()) {
int u = q.top().pos; q.pop();
if (vis[u]) continue;
vis[u] = true;
for (int e = head[u]; e; e = g[e].next) {
int v = g[e].to;
if (dis[v] > dis[u] + g[e].w) {
dis[v] = dis[u] + g[e].w;
q.push((node) { dis[v], v });
}
}
}
}

int main() {
cin >> n >> m >> p >> k;
for (int i = 1; i <= k; i ++) {
int x1, x2, y1, y2, g;
cin >> x1 >> y1 >> x2 >> y2 >> g;
if (g == 0) g = -1;
map[id(x1, y1)][id(x2, y2)] = g;
map[id(x2, y2)][id(x1, y1)] = g;
}
for (int l = 0; l < (1 << p); l ++) {
for (int i = 1; i <= n; i ++) { // x
for (int j = 1; j <= m; j ++) { // y
// right
if (j < m) {
int g = map[id(i, j)][id(i, j + 1)];
if (g == 0) {
add_edge(i, j, l, i, j + 1, l, 1);
add_edge(i, j + 1, l, i, j, l, 1);
}
if ((l & (1 << (g - 1))) && (g != 0) && (g != -1)) {
add_edge(i, j, l, i, j + 1, l, 1);
add_edge(i, j + 1, l, i, j, l, 1);
}
}
// down
if (i < n) {
int g = map[id(i, j)][id(i + 1, j)];
if (g == 0) {
add_edge(i, j, l, i + 1, j, l, 1);
add_edge(i + 1, j, l, i, j, l, 1);
}
if ((l & (1 << (g - 1))) && (g != 0) && (g != -1)) {
add_edge(i, j, l, i + 1, j, l, 1);
add_edge(i + 1, j, l, i, j, l, 1);
}
}
}
}
}
cin >> s;
for (int i = 1; i <= s; i ++) {
int x, y, q; cin >> x >> y >> q;
for (int l = 0; l < (1 << p); l ++) {
if (l & (1 << (q - 1))) continue;
add_edge(x, y, l, x, y, (l | (1 << (q - 1))), 0);
}
}
dijkstra();
int ans = 2e9;
for (int i = 1; i <= (1 << p); i ++) ans = min(ans, dis[m * n * i]);
if (ans == 2e9) ans = -1;
cout << ans << endl;
return 0;
}

感谢

感谢@nwt(在右边的友链里 wennitao),在debug的时候提供了莫大的帮助。(qhy是屑)

posted @ 2020-09-20 10:48  gyro永不抽风  阅读(233)  评论(0编辑  收藏  举报