##[$AcWing$ $844$. 走迷宫](https://www.acwing.com/problem/content/846/) ### 一、题目描述 给定一个 $n×m$ 的二维整数数组,用来表示一个迷宫,数组中只包含 $0$ 或 $1$,其中 $0$ 表示可以走的路,$1$ 表示不可通过的墙壁。 最初,有一个人位于左上角 $(1,1)$ 处,已知该人每次可以向上、下、左、右任意一个方向移动一个位置。 请问,该人从左上角移动至右下角 $(n,m)$ 处,至少需要移动多少次。 数据保证 $(1,1)$ 处和 $(n,m)$ 处的数字为 $0$,且一定至少存在一条通路。 **输入格式** 第一行包含两个整数 $n$ 和 $m$。 接下来 $n$ 行,每行包含 $m$ 个整数($0$ 或 $1$),表示完整的二维数组迷宫。 **输出格式** 输出一个整数,表示从左上角移动至右下角的最少移动次数。 **数据范围** $1≤n,m≤100$ **输入样例:** ```cpp {.line-numbers} 5 5 0 1 0 0 0 0 1 0 1 0 0 0 0 0 0 0 1 1 1 0 0 0 0 1 0 ``` **输出样例:** ```cpp {.line-numbers} 8 ``` ### 二、理解与感悟 1. $bfs$适合寻找最短(最长)的路径,因为是按层一层层找的,第一个符合条件的就是最短的路径。 2. 走迷宫一般使用$dx$,$dy$,就是左上右下,或者上右下左的二组变量常数,在蛇形排列中,还强调了四个方向的初始化方向,在走迷宫时,不强调顺序,哪个方向先来都是一样的。 3. 距离数组一般初始化为$-1$,表示未探索的位置。有时其它题中也表示不可以走的墙,具体问题再具体分析。 ```c++ memset(d,-1,sizeof d); ``` ### 三、$bfs$解法 ```cpp {.line-numbers} #include using namespace std; #define x first #define y second typedef pair PII; const int N = 110; int n, m; int g[N][N]; int d[N][N]; int dx[] = {-1, 0, 1, 0}; int dy[] = {0, 1, 0, -1}; queue q; int bfs() { q.push({1, 1}); memset(d, -1, sizeof d); d[1][1] = 0; while (q.size()) { if (~d[n][m]) break; // 不等于-1,表示已找到小哈,剪枝 auto u = q.front(); q.pop(); for (int i = 0; i < 4; i++) { int tx = u.x + dx[i], ty = u.y + dy[i]; if (tx == 0 || tx > n || ty == 0 || ty > m) continue; if (g[tx][ty] == 0 && d[tx][ty] == -1) { d[tx][ty] = d[u.x][u.y] + 1; q.push({tx, ty}); } } } return d[n][m]; } int main() { cin >> n >> m; for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) cin >> g[i][j]; cout << bfs() << endl; return 0; } ``` ### 四、加强版练习 走迷宫升级版——边的权值不同 单点时限: $2.0 sec$ 内存限制: $256 MB$ 一天,$sunny$ 不小心进入了一个迷宫,不仅很难寻找出路,而且有的地方还有怪物,但是 $sunny$ 有足够的能力杀死怪物,但是需要一定的时间,但是 $sunny$想早一点走出迷宫,所以请你帮助他计算出**最少的时间**走出迷宫,输出这个**最少时间**。 我们规定每走一格需要时间单位 $1$, 杀死怪物也需要时间 $1$, 如果不能走到出口,则输出 $impossible$. 每次走只能是上下左右 $4$ 个方向。 输入格式 每次首先 $2$ 个数 $n$,$m$ $(0 用户名:$littlehb$ 密码:旧密码 #### 思路 将每一点的坐标和由起点到该点的距离存入结构体. 由起点开始,将该点存入优先队列,以到起点的距离`dis`为优先级,每次取出`dis`最小的,向外扩散。 相当于第一轮得出所有到起点距离为`1`的点,第二轮得出所有到起点距离为`2`的点。 注意:对普通的最短路问题,由于每个各自的花费相同,因此每次存入的点优先级都相同. 故不需要使用优先队列,但本题存在有无怪物的区别,每次存入的格子的优先级可能不同,故使用优先队列。 ```cpp {.line-numbers} #include using namespace std; const int N = 210; struct Node { int x, y; // 坐标 int dis; // 距离 bool const operator<(const Node &b) const { return dis > b.dis; } }; // 问:优先队列默认是大顶堆,如果想距离小的在前怎么办呢? // 答:重载小于操作符。对比距离,谁的距离大谁就小。 char g[N][N]; // 地图 int sx, sy, tx, ty; // 起点坐标与终点坐标 int dx[] = {1, 0, -1, 0}; int dy[] = {0, 1, 0, -1}; int n, m; // 代表迷宫的高和宽 void bfs() { priority_queue q; // 在priority_queue中,优先队列默认是大顶堆 Node st{sx, sy, 0}; g[sx][sy] = '#'; q.push(st); while (q.size()) { Node u = q.top(); q.pop(); // 若已找到,则退出 if (u.x == tx && u.y == ty) { cout << u.dis << endl; return; } for (int i = 0; i < 4; i++) { int x = u.x + dx[i]; int y = u.y + dy[i]; Node t = {x, y, 0}; if (x >= 0 && x < n && y >= 0 && y < m && g[x][y] != '#') { if (g[x][y] == 'X') t.dis = u.dis + 2; else t.dis = u.dis + 1; // 走过就覆盖掉 g[t.x][t.y] = '#'; // 新结点入队列 q.push(t); } } } // 没有结果,输出不可能到达 printf("impossible\n"); } int main() { while (cin >> n >> m) { for (int i = 0; i < n; i++) cin >> g[i]; // 按字符串读入 for (int i = 0; i < n; i++) for (int j = 0; j < m; j++) { if (g[i][j] == 'S') sx = i, sy = j; if (g[i][j] == 'T') tx = i, ty = j; } bfs(); } return 0; } ```