题解:AtCoder AT_awc0129_d Maze and Trap Squares
【题目来源】
AtCoder:D - Maze and Trap Squares
【题目描述】
Takahashi is exploring a maze represented by a grid with \(H\) rows and \(W\) columns.
He needs to reach the goal G from the starting point S.
Each square contains one of the following characters:
S: Starting point (passable)G: Goal (passable)O: Normal square (passable, uppercase letter O)B: Wall (impassable)P: Trap square (passable)
Takahashi starts on the S square with a cumulative damage of \(0\).
In one move, he can advance from his current square to an adjacent square in one of the four directions (up, down, left, right). However, he cannot move outside the grid or onto a B square. He may pass through the same square any number of times.
When the destination square is P, the cumulative damage increases by \(1\) each time he enters that square. Even for the same P square, damage is added every time he enters it. Entering S, G, or O squares does not increase the damage.
The exploration ends immediately when Takahashi reaches the G square.
If a path from S to G exists, output the minimum cumulative damage upon reaching the goal.
If it is impossible to reach G regardless of how he moves, output -1.
高橋正在探索一个由 \(H\) 行 \(W\) 列的网格表示的迷宫。
他需要从起点 S 到达终点 G。
每个格子包含以下字符之一:
S:起点(可通过)G:终点(可通过)O:普通格子(可通过,大写字母 O)B:墙壁(不可通过)P:陷阱格子(可通过)
高橋从 S 格子出发,初始累积伤害为 \(0\)。
每次移动,他可以向当前格子的上下左右四个方向之一移动到相邻格子。但是,他不能移出网格边界或移动到 B 格子上。他可以多次经过同一个格子。
当目标格子是 P 时,每次进入该格子累积伤害增加 \(1\)。即使是同一个 P 格子,每次进入都会增加伤害。进入 S、G 或 O 格子不会增加伤害。
当高橋到达 G 格子时,探索立即结束。
如果存在从 S 到 G 的路径,输出到达终点时的最小累积伤害。
如果无论如何移动都无法到达 G,输出 -1。
【输入】
The input is given from standard input in the following format:
\(H\) \(W\)
\(A_1\)
\(A_2\)
\(\vdots\)
\(A_H\)
\(H, W\) are integers representing the number of rows and columns of the maze.
\(A_i \, (1 \leq i \leq H)\) is a string of length \(W\), where the \(j\)-th character \((1 \leq j \leq W)\) of \(A_i\) represents the type of the square at the \(i\)-th row from the top and the \(j\)-th column from the left.
【输出】
Output in one line the minimum cumulative damage received when traveling from S to G. If it is impossible to reach G, output -1.
【输入样例】
3 5
SPOOG
BBBBO
OOOOO
【输出样例】
1
【核心思想】
-
问题分析:给定 \(H \times W\) 网格迷宫,包含起点
S、终点G、普通格O、墙B、陷阱格P。每次进入P格累积伤害 \(+1\)(可重复进入重复累加),进入其他格不增加伤害。求从S到G的最小累积伤害,无法到达输出 \(-1\)。这是一个边权为 0/1 的最短路问题,关键在于将陷阱格建模为边权 \(1\)、其他格为边权 \(0\),用 Dijkstra 算法求最短路。 -
算法选择:
- Dijkstra 算法:网格中每条边的权重为 \(0\)(移动到
S/G/O)或 \(1\)(移动到P),求从S到G的最小累积伤害即求单源最短路 - 小根堆优化:使用
priority_queue配合greater实现小根堆,每次取出累积伤害最小的节点进行扩展 - 提前终止:当从堆中取出终点
G时,其距离已确定,可直接返回
- Dijkstra 算法:网格中每条边的权重为 \(0\)(移动到
-
关键步骤:
- 读入网格:读入 \(H, W\) 和网格 \(g[1..H][1..W]\),定位起点 \((sx, sy)\) 和终点 \((gx, gy)\)
- 初始化 Dijkstra:
dist数组初始化为 \(\infty\),`dist[sx][sy] = 0$,起点入堆 - 堆扩展循环:
- 取出堆顶节点 \((d, x, y)\),若 \(d > dist[x][y]\) 则跳过(已处理更优路径)
- 若 \((x, y) = (gx, gy)\),提前返回
- 遍历四邻域 \((nx, ny)\):
- 边界检查、墙
B跳过 - 计算新伤害
nd = d + (g[nx][ny] == 'P' ? 1 : 0) - 若
nd < dist[nx][ny],更新距离并入堆
- 边界检查、墙
- 输出结果:若
dist[gx][gy]仍为 \(\infty\),输出 \(-1\);否则输出该值
-
时间/空间复杂度:
- 时间复杂度:\(O(HW \cdot \log(HW))\),每个格子最多入堆一次,堆操作 \(O(\log(HW))\)
- 空间复杂度:\(O(HW)\),
dist数组、网格、优先队列
-
Dijkstra 在 0/1 边权网格中的核心思想:
- 伤害建模为边权:将"进入陷阱格增加伤害"转化为"移动到陷阱格的边权为 \(1\)",其他移动边权为 \(0\),问题转化为经典最短路
- 小根堆保证最优性:每次扩展累积伤害最小的节点,确保第一次到达终点时即为全局最小伤害(Dijkstra 的最优性原理)
- 懒删除优化:不立即从堆中删除过时节点,而是在取出时通过距离比较跳过,简化实现
- 提前终止:一旦取出终点即可返回,无需遍历整个网格,实际运行效率更高
- 适用于"网格移动代价不同(0/1 或正权)"的最短路径问题,核心在于将场景代价准确映射为边权,利用 Dijkstra 的最优性求解
【算法标签】
Dijkstra
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 2005, INF = 1e9; // N为网格最大尺寸,INF为极大值
int n, m; // n为行数,m为列数
char g[N][N]; // g[i][j]存储网格第i行第j列的字符
int dx[4] = {-1, 1, 0, 0}; // 四个方向的x偏移量:上、下、左、右
int dy[4] = {0, 0, -1, 1}; // 四个方向的y偏移量:上、下、左、右
int dist[N][N]; // dist[i][j]表示到达格子(i,j)的最小累积伤害
int sx = -1, sy = -1, gx = -1, gy = -1; // 起点S和终点G的坐标
// 定义优先队列节点:存储当前累积伤害和坐标
struct Node
{
int d, x, y; // d为到达该格子的累积伤害,x,y为坐标
};
// 重载大于运算符:使priority_queue成为小根堆(按累积伤害升序)
bool operator>(Node x, Node y)
{
return x.d > y.d; // 累积伤害小的优先
}
// Dijkstra算法:求从起点到终点的最小累积伤害
// 边权为0(普通格子)或1(陷阱格子),适合用Dijkstra
void dijkstra()
{
memset(dist, 0x3f, sizeof(dist)); // 将所有距离初始化为极大值(0x3f3f3f3f)
priority_queue<Node, vector<Node>, greater<Node>> pq; // 小根堆,按累积伤害排序
dist[sx][sy] = 0; // 起点累积伤害为0
pq.push({0, sx, sy}); // 将起点入队
while (!pq.empty()) // 当队列不为空时继续
{
auto t = pq.top(); // 取出累积伤害最小的节点
pq.pop();
int d = t.d, x = t.x, y = t.y; // 取出当前节点的伤害值和坐标
if (d > dist[x][y]) // 如果该节点已被更优路径访问过,跳过
continue;
if (x == gx && y == gy) // 如果到达终点
return; // 提前退出
for (int i = 0; i < 4; i++) // 枚举四个移动方向
{
int nx = x + dx[i]; // 计算相邻格子的行坐标
int ny = y + dy[i]; // 计算相邻格子的列坐标
// 边界检查:不能移出网格
if (nx < 1 || nx > n || ny < 1 || ny > m)
continue;
if (g[nx][ny] == 'B') // 墙壁不可通过
continue;
// 计算进入相邻格子的新累积伤害:如果是陷阱P则+1,否则+0
int nd = d + (g[nx][ny] == 'P' ? 1 : 0);
if (nd < dist[nx][ny]) // 如果找到更小的累积伤害路径
{
dist[nx][ny] = nd; // 更新最小累积伤害
pq.push({nd, nx, ny}); // 将新状态入队
}
}
}
}
int main()
{
cin >> n >> m; // 读入网格行数和列数
for (int i = 1; i <= n; i++) // 读入网格
for (int j = 1; j <= m; j++)
cin >> g[i][j];
// 遍历网格,找到起点S和终点G的坐标
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
{
if (g[i][j] == 'S') // 找到起点
sx = i, sy = j;
else if (g[i][j] == 'G') // 找到终点
gx = i, gy = j;
}
dijkstra(); // 执行Dijkstra算法求最小累积伤害
// 如果终点距离仍为极大值,说明无法到达
if (dist[gx][gy] == 0x3f3f3f3f)
cout << -1 << endl; // 输出-1表示无法到达
else
cout << dist[gx][gy] << endl; // 输出最小累积伤害
return 0;
}
【运行结果】
3 5
SPOOG
BBBBO
OOOOO
1
浙公网安备 33010602011771号