[蓝桥杯]迷宫与陷阱
迷宫与陷阱
题目描述
小明在玩一款迷宫游戏,在游戏中他要控制自己的角色离开一间由 N×NN×N 个格子组成的 2D 迷宫。
小明的起始位置在左上角,他需要到达右下角的格子才能离开迷宫。
每一步,他可以移动到上下左右相邻的格子中(前提是目标格子可以经过)。
迷宫中有些格子小明可以经过,我们用 '.' 表示。
有些格子是墙壁,小明不能经过,我们用 '#' 表示。
此外,有些格子上有陷阱,我们用 'X' 表示。除非小明处于无敌状态,否则不能经过。
有些格子上有无敌道具,我们用 '%' 表示。
当小明第一次到达该格子时,自动获得无敌状态,无敌状态会持续 KK 步。
之后如果再次到达该格子不会获得无敌状态了。
处于无敌状态时,可以经过有陷阱的格子,但是不会拆除/毁坏陷阱,即陷阱仍会阻止没有无敌状态的角色经过。
给定迷宫,请你计算小明最少经过几步可以离开迷宫?
输入描述
输入描述
第一行包含两个整数 N,K (1≤N≤1000,1≤K≤10)N,K (1≤N≤1000,1≤K≤10)。
以下 NN 行包含一个 N×NN×N 的矩阵。
矩阵保证左上角和右下角是 '.'。
输出描述
一个整数表示答案。如果小明不能离开迷宫,输出 -1。
输入输出样例
示例
输入
5 3
...XX
##%#.
...#.
.###.
.....
输出
10
运行限制
- 最大运行时间:3s
- 最大运行内存: 256M
总通过次数: 1649 | 总提交次数: 2154 | 通过率: 76.6%
难度: 困难 标签: 2018, 国赛, BFS
迷宫与陷阱问题:带状态BFS算法详解
算法思路
本问题需要在经典BFS基础上增加无敌状态的维度,核心思路是:
- 状态表示:每个位置
(x,y)叠加当前剩余无敌步数s,形成三元组(x, y, s)。不同无敌步数的同一位置视为不同状态。 - 无敌状态规则:
- 陷阱
X:仅当无敌步数s>0时可通行,通过后步数减1 - 道具
%:首次到达时重置无敌步数为K(覆盖原有状态),后续到达仅继承剩余步数
- 陷阱
- BFS扩展:从队列取出状态后,向四个方向扩展新状态:
- 计算基础无敌步数
ns_base = (s>0 ? s-1 : 0) - 根据格子类型调整最终无敌步数
ns
- 计算基础无敌步数
- 终止条件:首次到达终点
(N-1, N-1)时返回步数(BFS保证最小步数)
代码实现
#include <iostream>
#include <cstring>
using namespace std;
const int MAXN = 1005;
const int MAXK = 11; // K∈[1,10] 状态数0~K共11种
const int MAXS = MAXN * MAXN * MAXK; // 最大状态数
int n, K;
char grid[MAXN][MAXN];
bool got[MAXN][MAXN]; // 道具是否已获取
int dist[MAXN][MAXN][MAXK]; // dist[x][y][s] = 最小步数
int qx[MAXS], qy[MAXS], qs[MAXS]; // 数组模拟队列
int dx[4] = {1, 0, -1, 0};
int dy[4] = {0, 1, 0, -1};
int bfs() {
memset(dist, -1, sizeof(dist));
memset(got, false, sizeof(got));
int front = 0, rear = 0;
// 初始化起点
dist[0][0][0] = 0;
qx[rear] = 0;
qy[rear] = 0;
qs[rear] = 0;
rear++;
while (front < rear) {
int x = qx[front];
int y = qy[front];
int s = qs[front];
front++;
int step = dist[x][y][s];
// 到达终点
if (x == n-1 && y == n-1)
return step;
// 向四个方向扩展
for (int i = 0; i < 4; i++) {
int nx = x + dx[i];
int ny = y + dy[i];
// 边界检查
if (nx < 0 || nx >= n || ny < 0 || ny >= n)
continue;
// 遇到墙壁
if (grid[nx][ny] == '#')
continue;
// 遇到陷阱且无无敌状态
if (grid[nx][ny] == 'X' && s == 0)
continue;
// 计算基础无敌步数(移动消耗1步)
int ns_base = (s > 0) ? s - 1 : 0;
int ns = ns_base; // 默认值
// 道具格子处理
if (grid[nx][ny] == '%') {
if (!got[nx][ny]) {
got[nx][ny] = true; // 标记道具已获取
ns = K; // 重置无敌状态
}
}
// 状态未访问过则入队
if (dist[nx][ny][ns] == -1) {
dist[nx][ny][ns] = step + 1;
qx[rear] = nx;
qy[rear] = ny;
qs[rear] = ns;
rear++;
}
}
}
return -1; // 无法到达终点
}
int main() {
cin >> n >> K;
for (int i = 0; i < n; i++)
cin >> grid[i];
cout << bfs() << endl;
return 0;
}
代码解析
-
状态存储:
dist[x][y][s]:记录到达(x,y)且剩余无敌步数s的最小步数got[x][y]:标记道具格子是否已被获取- 数组
qx, qy, qs模拟队列,避免STL开销
-
核心逻辑:
- 陷阱处理(L38-40):仅当当前状态
s>0时允许通过 - 道具重置(L47-51):首次到达时重置无敌步数为
K - 状态更新(L54-60):仅当新状态未访问过时入队
- 陷阱处理(L38-40):仅当当前状态
-
终止条件(L28-29):首次到达终点立即返回(BFS性质保证最小步数)
实例验证
输入:
5 3
...XX
##%#.
...#.
.###.
.....
输出:10
执行过程解析:
- 起点
(0,0,0)→ 向右到(0,1,0)(步数1) - 继续向右到
(0,2,0)→ 向下到道具格(1,2,3)(重置无敌,步数3) - 利用无敌状态穿越陷阱:
(1,2,3)→(2,2,2)→(3,2,1)→(4,2,0)
- 向下移动:
(4,2,0)→(4,3,0)→(4,4,0)(步数10)
路径可视化:
https://example.com/maze_path.png
绿色路径为最优解,红色数字表示步数
注意事项
-
状态去重:
- 每个状态
(x,y,s)只扩展一次(dist数组保证) - 同一位置不同无敌步数视为独立状态
- 每个状态
-
道具全局性:
got数组标记道具获取状态(全局唯一)- 重置无敌状态会覆盖原有步数(非叠加)
-
边界情况:
- 起点和终点保证为
'.' - 当
K=1时需测试无敌状态传递 - 终点可能以不同无敌步数到达(取首次到达)
- 起点和终点保证为
测试点设计
| 测试类型 | 输入样例 | 预期输出 | 验证要点 |
|---|---|---|---|
| 基础样例 | 题目给定示例 | 10 | 标准流程 |
| 无解情况 | 全图被陷阱包围 | -1 | 终止条件 |
| 道具重置 | 多个道具格 | 最短路径 | 全局got标记 |
| 无敌传递 | K=1时连续过陷阱 | 正确步数 | 状态递减逻辑 |
| 边界规模 | N=1000, K=10的大地图 | 不超3s | 性能验证 |
优化建议
-
内存压缩:
- 使用
short dist[MAXN][MAXN][MAXK](步数<2000) - 用位运算压缩
got数组(每布尔值占1bit)
- 使用
-
搜索优化:
- 双向BFS(起点终点同时搜索)
- 优先扩展靠近终点的状态(A*启发式)
-
常数优化:
- 循环展开(手动展开方向循环)
- 使用局部变量缓存数组访问
复杂度分析:
时间:O(N²K) ≈ 1000²×11 = 1100万次操作
空间:O(N²K) ≈ 44MB(dist) + 132MB(队列)
更多推荐



所有评论(0)