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.0 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#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]; // 某个坐标位置上有哪些钥匙,这是用数位压缩记录的,方便位运算
int dist[N][1 << M]; // 哪个位置,在携带不同的钥匙情况下的状态
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() {
memset(dist, 0x3f, sizeof dist); // 初始化距离
queue<PII> q; // bfs用的队列
int t = get(1, 1); // 从编号1出发
q.push({t, key[t]}); // 位置+携带钥匙的压缩状态 = 现在的真正状态
dist[t][key[t]] = 0; // 初始状态的距离为0
while (q.size()) {
PII x = q.front();
q.pop();
int u = x.first; // 出发点编号
int st = x.second; // 钥匙状态
// 找到大兵瑞恩就结束了
if (u == n * m) return dist[u][st];
// 四个方向
for (int i = 0; i < 4; i++) {
// 注意这里我是将格子编号从1开始因此将其转化为坐标时需要先减一再加一
int tx = (u - 1) / m + 1 + dx[i]; // 下一个位置
int ty = (u - 1) % m + 1 + dy[i];
int tz = get(tx, ty); // 要去的坐标位置tz
int ts = st; // 复制出z结点携带过来的钥匙状态
/*
g[z][tz] == 0 有墙,不能走
g[z][tz] > 0 有门,有钥匙能走,无钥匙不能走
g[z][tz] == -1 随便走
*/
// 出界或有墙
if (tx == 0 || ty == 0 || tx > n || ty > m || g[u][tz] == 0) continue;
// 有门,并且, v这个状态中没有当前类型的钥匙
if (g[u][tz] > 0 && !(st >> g[u][tz] & 1)) continue;
// 捡起钥匙
ts |= key[tz];
// 如果这个状态没有走过
if (dist[tz][ts] == INF) {
q.push({tz, ts}); // 入队列
dist[tz][ts] = dist[u][st] + 1; // 步数加1
}
}
}
// 一直没走到,返回-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;
}