1944 年,特种兵麦克接到国防部的命令,要求立即赶赴太平洋上的一个孤岛,营救被敌军俘虏的大兵瑞恩。瑞恩被关押在一个迷宫里,迷宫地形复杂,但幸好麦克得到了迷宫的地形图。迷宫的外形是一个长方形,
其南北方向被划分为 n 行,东西方向被划分为 m 列,于是整个迷宫被划分为 n×m 个单元。每一个单元的位置可用一个有序数对 (单元的行号, 单元的列号) 来表示。南北或东西方向相邻的 2 个单元之间可能互通,也可能有一扇锁着的门,或者是一堵不可逾越的墙。迷宫中有一些单元存放着钥匙,并且所有的门被分成 p 类, 打开同一类的门的钥匙相同,不同类门的钥匙不同。
大兵瑞恩被关押在迷宫的东南角,即 (n,m) 单元里,并已经昏迷。迷宫只有一个入口, 在西北角。也就是说,麦克可以直接进入 (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
输出样例:
在这里给出相应的输出。例如:
14
分析
- 之前dfs写了这个没过,后来忘了写了现在想起来这个题了,补一个题;首先此题和T1215 拯救公主——bfs+三维数组标记+二进制状态压缩有点像,这题就是到达一个点,可能有门,需要你有钥匙,钥匙有很多种类,然后拥有不同钥匙状态在同一点的状态也不一样,那这就是状压bfs;状态k的二进制00001就表示有第1种门的钥匙,状态为10000表示有第5种门的钥匙;
- 用状压,就少不了位运算,一些位运算的操作含义: 1<<2 表示把左边的操作数1(0001)左移2位,那就是1*2^2=4(0001=》0100);| 就是二进制序列进行或运算;|= 就是二进制序列进行或运算,把运算结果赋值给左边;
- 1<<(x-1)是下面代码常见的操作,就是把操作数1的二进制序列的1向左移(x-1)位;
- bfs的逻辑就是,先判断是否越界、是否有墙挡着;然后判断,到达的点是否是门,是门的话(g[x][y][xx][yy]>0),看看当前的钥匙状态k,是否有这个门所需要的,k & 1 << (g[x][y][xx][yy] - 1),看看当前的钥匙状态 & 这个门所需要的钥匙状态,不为0就说明有这个门的钥匙;然后更新当前新状态kk(k | getKeyStatus(xx, yy)),在这个点(xx,yy)没访问过就加入队列;
- 此题的坑点:就是一个地方(x,y),可能有多把钥匙,所以三维key,存储是该点有几种钥匙;而且碰见这种输入的是两个点连通情况的,n比较小的话,直接四维数组存图;
#include<bits/stdc++.h>
using namespace std;
struct node {
int x, y, step, k;//k:当前钥匙的拥有状态
node(int xx, int yy, int stp, int kk) {
x = xx, y = yy, step = stp, k = kk;
}
};
int n, m, p, k, s, ans = 100;
int dx[] = {-1, 0, 1, 0};
int dy[] = {0, 1, 0, -1};
int g[15][15][15][15];
int vis[15][15][1 << 15];//第三维是钥匙的状压
int keyCnt[15][15];//每个位置的钥匙数
int key[15][15][15];//第三维就是该点有几种钥匙
queue<node> q;
//获取(x,y)的钥匙状态
int getKeyStatus(int x, int y) {
int ret = 0;
for (int i = 1; i <= keyCnt[x][y]; ++i) {
// |= 去组合这几种钥匙的合体状态(ret也就是拥有keyCnt[x][y]种钥匙的二进制序列的十进制数)
// 1<<1 《==》 1*(2^1) :1往左移的位数 比如第i把钥匙为1,那么二进制序列就是0001,就是 1 << (1-1)
ret |= (1 << (key[x][y][i] - 1));//1 << (key[x][y][i] - 1)表示当前这把钥匙类型的二进制的状态
}
return ret;
}
void bfs() {
vis[1][1][getKeyStatus(1, 1)] = 1;
q.push(node(1, 1, getKeyStatus(1, 1), 0));
while (!q.empty()) {
node current = q.front();
int x = current.x, y = current.y, step = current.step, k = current.k;
q.pop();
//到达终点
if (x == n && y == m) {
ans = step;
return;
}
for (int i = 0; i < 4; ++i) {
int xx = x + dx[i];
int yy = y + dy[i];
//不越界+有路
if (xx >= 1 && yy >= 1 && xx <= n && yy <= m && g[x][y][xx][yy] >= 0) {
//如果要到达的点有门,判断是否有这个门钥匙,看看当前的钥匙状态 & 这个门所需要的钥匙状态,不为0就说明有
if (g[x][y][xx][yy] && !(k & 1 << (g[x][y][xx][yy] - 1)))
continue;
//更新走到(xx,yy)的钥匙状态,因为(xx,yy)可能也有钥匙
int kk = k | getKeyStatus(xx, yy);// |运算一下,来把新钥匙加入原来的钥匙状态k,组成新的状态kk
if (!vis[xx][yy][kk]) {
q.push(node(xx, yy, step + 1, kk));
vis[xx][yy][kk] = 1;
}
}
}
}
}
int main() {
cin >> n >> m >> p >> k;
int a, b, c, d, e;
for (int i = 1; i <= k; ++i) {
cin >> a >> b >> c >> d >> e;
//无向图
if (e == 0)// 用-1表示墙
g[a][b][c][d] = g[c][d][a][b] = -1;
else
g[a][b][c][d] = g[c][d][a][b] = e;
}
cin >> s;
for (int i = 1; i <= s; ++i) {
cin >> a >> b >> c;
//(a,b)点的第keyCnt[a][b]的钥匙是c
key[a][b][++keyCnt[a][b]] = c;
}
bfs();
if (ans == 100)
cout << -1;
else
cout << ans;
return 0;
}