|
|
|
@ -77,12 +77,12 @@ $1≤k≤150$
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 二、解题思路
|
|
|
|
|
试想下如果本题 **没有钥匙和门** 的条件,只要求从 **左上角** 走到 **右下角** 的最小步数,就是简单的迷宫问题了,可以使用$BFS$解决。
|
|
|
|
|
试想下如果本题 **没有钥匙和门** 的条件,只要求从 **左上角** 走到 **右下角** 的最小步数,就是简单的迷宫问题了,可以使用$bfs$解决。
|
|
|
|
|
|
|
|
|
|
#### 状态表示
|
|
|
|
|
加上钥匙和门的的条件,便是**类似于八数码问题**了。实际上$BFS$解决的最短路问题都可以看作**求从初始状态到结束状态需要的最小转移次数**:
|
|
|
|
|
加上钥匙和门的的条件,便是**类似于八数码问题**了。实际上$bfs$解决的最短路问题都可以看作 **求从初始状态到结束状态需要的最小转移次数**:
|
|
|
|
|
|
|
|
|
|
普通迷宫问题的 **状态** 就是 **当前所在的坐标**,八数码问题的 **状态** 就是**当前棋盘的局面**。
|
|
|
|
|
普通迷宫问题的 **状态** 就是 **当前所在的坐标**,八数码问题的 **状态** 就是 **当前棋盘的局面**。
|
|
|
|
|
|
|
|
|
|
本题在迷宫问题上加上了 **钥匙和门** 的条件,显然,处在同一个坐标下,**持有钥匙和不持有钥匙就不是同一个状态了**,为了能够清楚的表示每个状态,除了当前坐标外还需要加上当前获得的钥匙信息,即$f[x][y][st]$表示当前处在$(x,y)$位置下持有钥匙状态为$st$,将二维坐标压缩成一维就得到$f[z][st]$这样的状态表示了,或者说,$z$是格子的编号,从上到下,从左而右的编号依次为$1$到$n*m$,$st$为$0110$时,表示持有第$1,2$类钥匙,这里注意我在 <font color='red'><b>表示状态时抛弃了最右边的一位</b></font>,因为钥匙编号从$1$开始,我想确定是否持有第$i$类钥匙时,只需要判断`st >> i & 1`是不是等于$1$即可。
|
|
|
|
|
|
|
|
|
@ -108,7 +108,7 @@ typedef pair<int, int> PII;
|
|
|
|
|
|
|
|
|
|
int g[N][N]; // 两个位置之间的间隔是什么,可能是某种门,或者是墙
|
|
|
|
|
int key[N]; // 某个坐标位置上有哪些钥匙,这是用数位压缩记录的,方便位运算
|
|
|
|
|
int dist[N][1 << M]; // 哪个位置,在携带不同的钥匙情况下的状态
|
|
|
|
|
int dis[N][1 << M]; // 哪个位置,在携带不同的钥匙情况下的状态
|
|
|
|
|
int n, m; // n行m列
|
|
|
|
|
int k; // 迷宫中门和墙的总数
|
|
|
|
|
int p; // p类钥匙
|
|
|
|
@ -121,22 +121,23 @@ int get(int x, int y) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int bfs() {
|
|
|
|
|
memset(dist, 0x3f, sizeof dist); // 初始化距离
|
|
|
|
|
queue<PII> q; // bfs用的队列
|
|
|
|
|
memset(dis, 0x3f, sizeof dis); // 初始化距离
|
|
|
|
|
queue<PII> q; // bfs用的队列
|
|
|
|
|
|
|
|
|
|
int t = get(1, 1); // 从编号1出发
|
|
|
|
|
q.push({t, key[t]}); // 位置+携带钥匙的压缩状态 = 现在的真正状态
|
|
|
|
|
dist[t][key[t]] = 0; // 初始状态的距离为0
|
|
|
|
|
int S = get(1, 1); // 从编号1出发
|
|
|
|
|
q.push({S, key[S]}); // 位置+携带钥匙的压缩状态 = 现在的真正状态
|
|
|
|
|
dis[S][key[S]] = 0; // 初始状态的需要走的步数为0
|
|
|
|
|
|
|
|
|
|
while (q.size()) {
|
|
|
|
|
PII x = q.front();
|
|
|
|
|
q.pop();
|
|
|
|
|
|
|
|
|
|
int u = x.first; // 出发点编号
|
|
|
|
|
int st = x.second; // 钥匙状态
|
|
|
|
|
int st = x.second; // 钥匙状态,为状态压缩的数字
|
|
|
|
|
|
|
|
|
|
// 找到大兵瑞恩就结束了
|
|
|
|
|
if (u == n * m) return dist[u][st];
|
|
|
|
|
// dis[u][st]:到达了n*m,并且,当前状态是st: 找到大兵瑞恩就结束了,不用管最终的钥匙状态是什么
|
|
|
|
|
// 是什么都是符合拯救大兵的目标的
|
|
|
|
|
if (u == n * m) return dis[u][st];
|
|
|
|
|
|
|
|
|
|
// 四个方向
|
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
|
|
@ -144,27 +145,26 @@ int bfs() {
|
|
|
|
|
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结点携带过来的钥匙状态
|
|
|
|
|
int T = get(tx, ty); // 要去的坐标位置T
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
g[z][tz] == 0 有墙,不能走
|
|
|
|
|
g[z][tz] > 0 有门,有钥匙能走,无钥匙不能走
|
|
|
|
|
g[z][tz] == -1 随便走
|
|
|
|
|
g[z][T] == 0 有墙,不能走
|
|
|
|
|
g[z][T] > 0 有门,有钥匙能走,无钥匙不能走
|
|
|
|
|
g[z][T] == -1 随便走
|
|
|
|
|
*/
|
|
|
|
|
// 出界或有墙
|
|
|
|
|
if (tx == 0 || ty == 0 || tx > n || ty > m || g[u][tz] == 0) continue;
|
|
|
|
|
// 出界或有墙,没有办法转移
|
|
|
|
|
if (tx == 0 || ty == 0 || tx > n || ty > m || g[u][T] == 0) continue;
|
|
|
|
|
|
|
|
|
|
// 有门,并且, v这个状态中没有当前类型的钥匙
|
|
|
|
|
if (g[u][tz] > 0 && !(st >> g[u][tz] & 1)) continue;
|
|
|
|
|
// 有门,并且, st这个状态中没有带过来当前类型的钥匙
|
|
|
|
|
if (g[u][T] > 0 && !(st >> g[u][T] & 1)) continue;
|
|
|
|
|
|
|
|
|
|
// 捡起钥匙
|
|
|
|
|
ts |= key[tz];
|
|
|
|
|
// 捡起钥匙不会增加成本,所以,无条件捡起来钥匙
|
|
|
|
|
int ST = st | key[T];
|
|
|
|
|
|
|
|
|
|
// 如果这个状态没有走过
|
|
|
|
|
if (dist[tz][ts] == INF) {
|
|
|
|
|
q.push({tz, ts}); // 入队列
|
|
|
|
|
dist[tz][ts] = dist[u][st] + 1; // 步数加1
|
|
|
|
|
if (dis[T][ST] == INF) {
|
|
|
|
|
q.push({T, ST}); // 入队列
|
|
|
|
|
dis[T][ST] = dis[u][st] + 1; // 步数加1
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -204,4 +204,5 @@ int main() {
|
|
|
|
|
printf("%d\n", bfs());
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|