You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

105 lines
4.1 KiB

2 years ago
#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 105, M = 12;
typedef pair<int, int> PII;
int g[N][N]; // 两个位置之间的间隔是什么,可能是某种门,或者是墙
int key[N]; // 某个坐标位置上有哪些钥匙,这是用数位压缩记录的,方便位运算
2 years ago
int dis[N][1 << M]; // 哪个位置,在携带不同的钥匙情况下的状态
2 years ago
int n, m; // n行m列
int k; // 迷宫中门和墙的总数
int p; // p类钥匙
int dx[] = {-1, 0, 1, 0}; // 上右下左
int dy[] = {0, 1, 0, -1}; // 上右下左
// 二维转一维的办法,坐标从(1,1)开始
int get(int x, int y) {
return (x - 1) * m + y;
}
int bfs() {
2 years ago
memset(dis, 0x3f, sizeof dis); // 初始化距离
queue<PII> q; // bfs用的队列
2 years ago
2 years ago
int S = get(1, 1); // 从编号1出发
q.push({S, key[S]}); // 位置+携带钥匙的压缩状态 = 现在的真正状态
dis[S][key[S]] = 0; // 初始状态的需要走的步数为0
2 years ago
while (q.size()) {
PII x = q.front();
q.pop();
int u = x.first; // 出发点编号
2 years ago
int st = x.second; // 钥匙状态,为状态压缩的数字
2 years ago
2 years ago
// dis[u][st]:到达了n*m,并且当前状态是st: 找到大兵瑞恩就结束了,不用管最终的钥匙状态是什么
// 是什么都是符合拯救大兵的目标的
if (u == n * m) return dis[u][st];
2 years ago
// 四个方向
for (int i = 0; i < 4; i++) {
// 注意这里我是将格子编号从1开始因此将其转化为坐标时需要先减一再加一
int tx = (u - 1) / m + 1 + dx[i]; // 下一个位置
int ty = (u - 1) % m + 1 + dy[i];
2 years ago
int T = get(tx, ty); // 要去的坐标位置T
2 years ago
/*
2 years ago
g[z][T] == 0
g[z][T] > 0
g[z][T] == -1 便
2 years ago
*/
2 years ago
// 出界或有墙,没有办法转移
if (tx == 0 || ty == 0 || tx > n || ty > m || g[u][T] == 0) continue;
2 years ago
2 years ago
// 有门,并且, st这个状态中没有带过来当前类型的钥匙
if (g[u][T] > 0 && !(st >> g[u][T] & 1)) continue;
2 years ago
2 years ago
// 捡起钥匙不会增加成本,所以,无条件捡起来钥匙
int ST = st | key[T];
2 years ago
// 如果这个状态没有走过
2 years ago
if (dis[T][ST] == INF) {
q.push({T, ST}); // 入队列
dis[T][ST] = dis[u][st] + 1; // 步数加1
2 years ago
}
}
}
// 一直没走到,返回-1
return -1;
}
int main() {
scanf("%d %d %d %d", &n, &m, &p, &k); // 地图n行m列p类钥匙,k:迷宫中门和墙的总数
// 地图初始化
memset(g, -1, sizeof g);
// 1、将图记下来
int x1, y1, x2, y2, z1, z2, z;
while (k--) { // 读入迷宫中门和墙
// z=0:墙z ={1,2,3,4...}哪种类型的门
scanf("%d %d %d %d %d", &x1, &y1, &x2, &y2, &z);
// 二维转化一维
// Q:为什么一定要二维转一维呢?不转直接用二维不行吗?
// A:点是一个二维信息,将坐标转换为点的编号,这样数组不用声明更多维度,代码更简单
z1 = get(x1, y1), z2 = get(x2, y2);
// 记录两个位置之间的间隔是:墙?某种类型的门?
g[z1][z2] = g[z2][z1] = z; // 无向图
}
int s;
scanf("%d", &s);
while (s--) { // 枚举每把钥匙
scanf("%d %d %d", &x1, &y1, &z); // 位置+钥匙类型
// 利用状态压缩,描述此位置上有钥匙
// get(x1,y1)--->1维转换后的数字
// key[get(x1,y1)]利用二进制+位运算,记录此位置有哪些类型的钥匙,因为一个位置可以有多个类型的钥匙,所以采用 |运算符进行累加
// 1 << z 放过最后一位将倒数第z位设置为数字1
key[get(x1, y1)] |= 1 << z;
}
// 宽搜
printf("%d\n", bfs());
return 0;
}