题目来源:改编自 LeetCode 1631 最小体力消耗路径。
学习目标
前置知识与环境
先阅读最小生成树。需要 C++17 编译器,运行前可执行 make doctor。
题目
在高度网格中从左上到右下,每次只能上下左右移动。路径体力值是相邻格子高度差绝对值的最大值,求可能的最小体力值。单格路径体力为 0。练习用 Kruskal 按高度差合并;也可实现二分阈值加连通性判定作对照。
输入格式
第一行 rows cols,随后 rows 行、每行 cols 个高度。1 ≤ rows,cols ≤ 100,1 ≤ height ≤ 10^6。
输出格式
输出最小体力值。
样例输入
3 3
1 2 2
3 8 2
5 3 5样例输出
2样例解释
沿高度 1→3→5→3→5 行走,相邻高度差均不超过 2;阈值 1 时起终点不能连通,所以答案为 2。
任务
- 每对右邻、下邻格子只建一条无向边。
- 按高度差升序合并端点。
- 起终点首次连通时的边权就是答案。
在 student/main.cpp 的 TODO 处完成算法。起始代码可以编译,但不代表已经实现题目;参考实现位于 solution/main.cpp。
正常、边界与错误情况
目标是最大边权的最小值,不能把路径高度差相加。
输入保证符合上述格式与范围,不要求处理损坏输入。无解、不可达等情况按本题输出合同处理,不视为格式错误。测试点包含如下场景,每点 5 分,共 100 分:
| 测试点 | 类型 |
|---|---|
001-sample | 样例 |
002-singleton | 边界 |
003-flat | 边界 |
004-single-row | 边界 |
005-single-column | 边界 |
006-detour-beats-direct | 易错反例 |
007-bottleneck-not-sum | 易错反例 |
008-max-height-gap | 边界 |
009-seeded-grid-0 | 正常 |
010-seeded-grid-1 | 正常 |
011-seeded-grid-2 | 正常 |
012-seeded-grid-3 | 正常 |
013-seeded-grid-4 | 正常 |
014-seeded-grid-5 | 正常 |
015-seeded-grid-6 | 正常 |
016-seeded-grid-7 | 正常 |
017-seeded-grid-8 | 正常 |
018-max-grid | 规模 |
019-checkerboard | 规模 |
020-long-corridor | 规模 |
运行与评分
在本 Lab 目录执行:
make doctor
make run CASE=001-sample
make score未安装 Make 时,在仓库根目录执行:
pnpm lab:doctor -- labs/chapter-07/exercise/E-07-15-minimum-effort-path
pnpm lab:run -- labs/chapter-07/exercise/E-07-15-minimum-effort-path --case 001-sample
pnpm lab:score -- labs/chapter-07/exercise/E-07-15-minimum-effort-path解题思路
把网格相邻格子连边,边权是高度差绝对值。用 Dijkstra 最小化路径上的最大边权:松弛时取 max(current, edge),第一次确定终点时即得到最小体力。
复杂度分析
令 V=rows×cols,网格边数 O(V),Kruskal 时间 O(V log V),空间 O(V)。二分+BFS 为 O(V log H),H 是高度差上界。
完成清单
思考与复盘
- 本题最容易遗漏的约束是什么?
参考思路
目标是最大边权的最小值,不能把路径高度差相加。
- 为什么松弛时使用
max(dist[u], effort(u,v))而不是加法?这与题目中的“路径体力”定义有什么关系?
参考思路
路径体力由经过的最大高度差决定,而不是高度差总和。到达 v 的候选代价应是当前路径最大边权与新边权的较大者,再在候选中取最小值。