📅  最后修改于: 2023-12-03 15:42:20.885000             🧑  作者: Mango
本题是 ACM 国际大学生程序设计竞赛(简称 ICPC)的一道历史经典题目,也是一个经典的迷宫问题。本题目难度较高,需要求解者有一定的编程基础和算法基础。
在一个迷宫中,有一个门到另一个门之间需要走过一段路程。给定一个 $N\times M$ 的矩形地图,其中 $N$ 表示地图的行数,$M$表示地图的列数。地图上包含三种类型的格子:门、石头和道路。其中,门是起点和终点,石头不能穿过,道路可以穿过。
现在,给定地图的起点和终点,请计算出穿过石头花费的最小步数。
输入的第一行包含两个整数 $N$ 和 $M$,表示地图的行数和列数。
接下来的 $N$ 行,每行包含 $M$ 个字符。其中,字符 'S' 表示起点,字符 'T' 表示终点,字符 '#' 表示石头,字符 '.' 表示道路。
输出一个整数,表示穿过石头的最小步数。
输入:
5 5
#####
#.#.#
#.#.#
#..#T
#..#S
输出:
10
本题需要求解从起点到终点穿过石头的最小步数,即需要使用一种较为高效的算法来解决。
一种常用的算法是 Dijkstra 算法,该算法可以用来求单源最短路径。它基于贪心法的思想,每次都选择当前最短的路径进行扩展,直到扩展到终点为止。
本题可以将地图看成一个有向网格图,每个点向四个方向连有向边,边的权值为到该点所需要的步数。起点为图的源点,终点为图的汇点。
具体地,可以使用一个二维数组 $dist$ 来表示到每个点的最短距离。初始值均为 $\infty$,表示还未到达该点。起点的距离设为0。使用一个集合 $S$ 来保存已经确定了最短距离的节点。每次从 $dist$ 中选择一个距离最短的点 $u$,将 $u$ 加入 $S$。然后用 $u$ 更新其他点到起点的最短距离,如果更新后的距离更短,就更新 $dist$,并将该点加入集合 $S$。重复执行以上过程,直到 $dist[T]$ 被确定。最后输出 $dist[T]$ 即可。
由于本题的边权不一定是正整数,可以将具体的边权看成两个部分:第一部分是到该点所需要的步数 $step$,第二部分是穿过石头需要的时间 $time$,即 $cost=step + time \times f(dx,dy)$,其中 $f(dx,dy)$ 表示对于该点的位置 $(dx,dy)$,需要穿过多少个石头,该值可以使用 BFS 算法预处理出来。采用 Dijkstra 算法,每个待扩展节点的优先级即为到该点的总花费。
// 计算某个点到起点的最短距离
void dijkstra() {
memset(dis, INF, sizeof(dis));
memset(vis, false, sizeof(vis));
dis[sx][sy] = 0; // 起点距离设为0
priority_queue<Node> q; // 优先队列,按花费贪心选择节点
q.push(Node(sx, sy, 0));
while(!q.empty()) {
Node cur = q.top();
q.pop();
if(vis[cur.x][cur.y]) {
continue;
}
vis[cur.x][cur.y] = true;
// 对四个方向逐一松弛
for(int i = 0; i < 4; i++) {
int nx = cur.x + dx[i], ny = cur.y + dy[i];
if(nx < 1 || ny < 1 || nx > n || ny > m) { // 越界
continue;
}
int step = dis[cur.x][cur.y] + 1, cost = step + f[nx][ny] * t; // 计算边权
if(grid[nx][ny] == '#' || dis[nx][ny] <= cost) { // 无法松弛或者遇到障碍
continue;
}
dis[nx][ny] = cost;
q.push(Node(nx, ny, cost));
}
}
}
本题代码较长,代码实现细节可以具体学习参考。