0题目原文(本地存了一份)
原题在洛谷上(页头有链接)。别人的网站不归我们管,打不开、改版、题号调整都可能发生。 所以每个解析页都把题面转录一份存在本地,跟着仓库一起进版本库。
转录自洛谷 P1462,日期见页头。两边不一致时信原站。
题目背景
在艾泽拉斯大陆上有一位名叫歪嘴哦的神奇术士,他是部落的中坚力量。 有一天他醒来后发现自己居然到了联盟的主城暴风城。 在被众多联盟的士兵攻击后,他决定逃回自己的家乡奥格瑞玛。
题目描述
在艾泽拉斯,有 n 个城市。编号为 1, 2, 3, …, n。
城市之间有 m 条双向的公路,连接着两个城市,从某个城市到另一个城市,
会遭到联盟的攻击,进而损失一定的血量。
每次经过一个城市,都会被收取一定的过路费(包括起点和终点)。 路上并没有收费站。
假设 1 为暴风城,n 为奥格瑞玛,而他的血量最多为 b,出发时他的血量是满的。
如果他的血量降低至负数,则他就无法到达奥格瑞玛。
歪嘴哦不希望花很多钱,他想知道,在所有可以到达奥格瑞玛的道路中, 对于每条道路所经过的城市单次收费的最大值,其最小值为多少。
输入格式
第一行 3 个正整数,n, m, b。分别表示有 n 个城市,m 条公路,歪嘴哦的血量为 b。
接下来有 n 行,每行 1 个非负整数 fᵢ。表示经过城市 i,需要交费 fᵢ 元。
再接下来有 m 行,每行 3 个正整数 aᵢ, bᵢ, cᵢ(1 ≤ aᵢ, bᵢ ≤ n)。
表示城市 aᵢ 和城市 bᵢ 之间有一条公路,如果从城市 aᵢ 到城市 bᵢ,
或者从城市 bᵢ 到城市 aᵢ,会损失 cᵢ 的血量。
输出格式
仅一个整数,表示歪嘴哦经过城市单次交费最大值的最小值。
如果他无法到达奥格瑞玛,输出 AFK。
说明/提示
对于 60% 的数据,满足 n ≤ 200,m ≤ 10⁴,b ≤ 200;
对于 100% 的数据,满足 1 ≤ n ≤ 10⁴,1 ≤ m ≤ 5 × 10⁴,1 ≤ b ≤ 10⁹;
对于 100% 的数据,满足 1 ≤ cᵢ ≤ 10⁹,0 ≤ fᵢ ≤ 10⁹,可能有两条边连接着相同的城市。
时限 1 秒,内存 512 MB。
输入输出样例
输入
4 4 8 8 5 6 10 2 1 2 2 4 1 1 3 4 3 4 3
输出
10
四个城市的过路费是 8 5 6 10,血量 8。
走 1 → 2 → 4 掉血 2 + 1 = 3(够),这条路上的城市是 1、2、4,
过路费 max(8, 5, 10) = 10。⇒ 答案 10。
⚠ 注意终点 4 号那 10 块钱也要算 —— 这一条正是第 ② 步那个错法栽的地方。
1★ 关键的一步:把「最大值的最小值」二分掉
只准走过路费
f[i] ≤ mid的城市,问 1 → n 的最短(掉血)路是不是 ≤ b。
—— 而这一问,正是本章的 Dijkstra(边权是掉血 c,点权 f 只用来决定哪些点能走)。
⇒ 二分 + Dijkstra,O(m log n × log V)。
这是第 9 章那套二分答案在图上的第一次登场。
★ 答案一定是某个 f[i](它就是路径上某个点的费用),所以二分在排序去重后的 f 值上做,
一次都不用去猜值域。
// P1462 通往奥格瑞玛的道路 —— ★ 这一版就能 AC//// 题目要的是:在所有「血量够用」的路线里,**路径上单次过路费最大值**的**最小值**。//// ★ 关键的一步:**「最大值的最小值」⇒ 二分那个最大值**(第 9 章那一整章)。// 固定一个上限 mid 之后,题目当场塌成一句话:// 只准走过路费 f[i] ≤ mid 的城市,问 1 → n 的最短(掉血)路是不是 ≤ b。// —— 而这一问,正是本章的 Dijkstra。//// ⇒ 二分 + Dijkstra,`O(m log n × log V)`。这是第 9 章那套二分答案在图上的第一次登场。//// ★ 单调性(二分的前提)为什么成立:mid 越大,能走的城市**只多不少** ⇒ 最短路只会更短// ⇒ 「可行」一旦成立,往上就一直成立。度量程序把**所有阈值逐个试了一遍**,// 300 轮全都是「一串 0 之后全是 1」,没有一轮跳来跳去(第 9 章那条:单调性要验,不能默认)。//// ⚠ 三处一不小心就写错的地方,各配了一份错误版本:// ① **过路费包括起点和终点** —— f[1] 和 f[n] 也要 ≤ mid(p1462NoEnd.cpp);// ② 「血量降低至**负数**才不行」⇒ 判据是 `dist <= b`,**不是** `dist < b`(p1462Strict.cpp);// ③ 掉血总和:路径最多 n−1 = 10⁴ 条边、每条 10⁹ ⇒ **10¹³**,必须 long long(p1462Int.cpp)。//// ⚠ 答案一定是某个 f[i](它就是路径上的某个点的费用),所以二分**在排序去重后的 f 值上做**。
#include <bits/stdc++.h>using namespace std;typedef long long ll;typedef pair<ll, int> PLI;
const ll INF = (ll)4e18;
int n, m;ll b;vector<int> f;vector<vector<pair<int, int>>> g;
/* 只准走 f[i] <= lim 的城市,问 1 → n 的最短掉血是多少 */static ll shortest(int lim) { if (f[1] > lim || f[n] > lim) return INF; // ★ 起点和终点也要交钱 vector<ll> dist(n + 1, INF); priority_queue<PLI, vector<PLI>, greater<PLI>> q; dist[1] = 0; q.push({0, 1}); while (!q.empty()) { auto [d, u] = q.top(); q.pop(); if (d > dist[u]) continue; for (auto [v, w] : g[u]) { if (f[v] > lim) continue; // 这座城市交不起,绕开 if (d + w < dist[v]) { dist[v] = d + w; q.push({dist[v], v}); } } } return dist[n];}
int main() { ios::sync_with_stdio(false); cin.tie(nullptr);
if (!(cin >> n >> m >> b)) return 0; f.assign(n + 1, 0); for (int i = 1; i <= n; i++) cin >> f[i]; g.assign(n + 1, {}); for (int i = 0; i < m; i++) { int u, v, w; cin >> u >> v >> w; g[u].push_back({v, w}); g[v].push_back({u, w}); // 题面:双向公路 }
vector<int> cand(f.begin() + 1, f.end()); sort(cand.begin(), cand.end()); cand.erase(unique(cand.begin(), cand.end()), cand.end());
int lo = 0, hi = (int)cand.size() - 1, ans = -1; while (lo <= hi) { // 二分「最大值」的那个下标 int mid = (lo + hi) / 2; if (shortest(cand[mid]) <= b) { ans = cand[mid]; hi = mid - 1; } // ★ <= 不是 < else lo = mid + 1; } if (ans < 0) cout << "AFK\n"; else cout << ans << '\n'; return 0;}点「运行 ▶」看结果
mid 越大,能走的城市只多不少 ⇒ 最短掉血只会更小 ⇒ 「可行」一旦成立就一直成立。
度量程序把所有候选阈值逐个试了一遍(不是二分,是从小到大全跑), 看可行性是不是「一串 0 之后全是 1」:300 / 300 轮都是,没有一轮跳来跳去。
⇒ 第 9 章那条:单调性是二分能用的全部理由,它要验,不能默认。
2⚠ 第一个错法:忘了「过路费包括起点和终点」
// ✗ 错法①:只限制**中间**的城市,忘了「过路费包括起点和终点」//// 题面原话:「每次经过一个城市,都会被收取一定的过路费(**包括起点和终点**)。」// 那半句括号是出题人专门加的,而顺手写 check 时最容易只写「走到 v 之前看一眼 f[v]」,// 结果 f[1] 和 f[n] 谁也没查。//// ★ 它的方向是可判的:少了两个限制 ⇒ 可行的 mid 只多不少 ⇒ **答案恒 ≤ 正解**// ([第 26 章那条判据](/sol/p1220/):解的是一个放宽了的问题)。300 轮实测一次没反过来。//// ⚠ 而官方样例**当场就挡住它**:那组数据里起点 f[1] = 8、终点 f[4] = 10,// 正解就是被终点顶上去的 10,而这一版只看中间那个 5。
#include <bits/stdc++.h>using namespace std;typedef long long ll;typedef pair<ll, int> PLI;
const ll INF = (ll)4e18;
int n, m;ll b;vector<int> f;vector<vector<pair<int, int>>> g;
static ll shortest(int lim) { /* ⚠ 少的就是这两句:if (f[1] > lim || f[n] > lim) return INF; */ vector<ll> dist(n + 1, INF); priority_queue<PLI, vector<PLI>, greater<PLI>> q; dist[1] = 0; q.push({0, 1}); while (!q.empty()) { auto [d, u] = q.top(); q.pop(); if (d > dist[u]) continue; for (auto [v, w] : g[u]) { if (v != n && f[v] > lim) continue; // ⚠ 连终点都放行了 if (d + w < dist[v]) { dist[v] = d + w; q.push({dist[v], v}); } } } return dist[n];}
int main() { ios::sync_with_stdio(false); cin.tie(nullptr); if (!(cin >> n >> m >> b)) return 0; f.assign(n + 1, 0); for (int i = 1; i <= n; i++) cin >> f[i]; g.assign(n + 1, {}); for (int i = 0; i < m; i++) { int u, v, w; cin >> u >> v >> w; g[u].push_back({v, w}); g[v].push_back({u, w}); } vector<int> cand(f.begin() + 1, f.end()); sort(cand.begin(), cand.end()); cand.erase(unique(cand.begin(), cand.end()), cand.end()); int lo = 0, hi = (int)cand.size() - 1, ans = -1; while (lo <= hi) { int mid = (lo + hi) / 2; if (shortest(cand[mid]) <= b) { ans = cand[mid]; hi = mid - 1; } else lo = mid + 1; } if (ans < 0) cout << "AFK\n"; else cout << ans << '\n'; return 0;}点「运行 ▶」看结果
题面那半句括号「(包括起点和终点)」是出题人专门加的,
而顺手写 check 时最容易只写「走到 v 之前看一眼 f[v]」,结果 f[1] 和 f[n] 谁也没查。
| 300 轮 | 顺手随机 | ★ f 全相同那一档 |
|---|---|---|
| 被抓 | 179 | ★ 精确的 0 |
| 其中答案比正解大 | ★ 0 | 0 |
★ 「比正解大的是 0」是可判的:少了两个限制 ⇒ 可行的 mid 只多不少 ⇒ 答案恒 ≤ 正解
(第 26 章那条判据)。
★ 而 f 全相同那一档是结构性的 0:候选值只有一个,查不查两头都一样。
⚠ 而这是这一页唯一一个被官方样例挡住的错法 ——
因为那组数据的起点 f[1] = 8、终点 f[4] = 10,答案 10 正是被终点顶上去的。
3★★ 第二个错法:血量判据写成 `<` —— 一条只有一个点宽的线
// ✗ 错法②:血量判据写成 `dist < b`(严格小于)//// 题面原话:「他的血量最多为 b,出发时他的血量是满的。**如果他的血量降低至负数,// 则他就无法到达奥格瑞玛。**」//// ⇒ 掉血正好等于 b 时,血量是 **0**,**0 不是负数** ⇒ 这条路**是可行的**。// 判据必须是 `dist <= b`。//// ⚠ 这是一个只在**一条线上**才现形的 off-by-one:触发条件是「最短掉血恰好等于 b」。// ⇒ 顺手随机(b 从一个大范围里抽)几乎撞不上;// ★ 而把 b **对齐到某条真实路径的长度**上,它当场 300 / 300 全错(生成器档位 1)。// ⇒ 又一次「[边界要跨过那条具体的线](/sol/p1182/)」:这条线不是「大概附近」,是**恰好那一个值**。
#include <bits/stdc++.h>using namespace std;typedef long long ll;typedef pair<ll, int> PLI;
const ll INF = (ll)4e18;
int n, m;ll b;vector<int> f;vector<vector<pair<int, int>>> g;
static ll shortest(int lim) { if (f[1] > lim || f[n] > lim) return INF; vector<ll> dist(n + 1, INF); priority_queue<PLI, vector<PLI>, greater<PLI>> q; dist[1] = 0; q.push({0, 1}); while (!q.empty()) { auto [d, u] = q.top(); q.pop(); if (d > dist[u]) continue; for (auto [v, w] : g[u]) { if (f[v] > lim) continue; if (d + w < dist[v]) { dist[v] = d + w; q.push({dist[v], v}); } } } return dist[n];}
int main() { ios::sync_with_stdio(false); cin.tie(nullptr); if (!(cin >> n >> m >> b)) return 0; f.assign(n + 1, 0); for (int i = 1; i <= n; i++) cin >> f[i]; g.assign(n + 1, {}); for (int i = 0; i < m; i++) { int u, v, w; cin >> u >> v >> w; g[u].push_back({v, w}); g[v].push_back({u, w}); } vector<int> cand(f.begin() + 1, f.end()); sort(cand.begin(), cand.end()); cand.erase(unique(cand.begin(), cand.end()), cand.end()); int lo = 0, hi = (int)cand.size() - 1, ans = -1; while (lo <= hi) { int mid = (lo + hi) / 2; if (shortest(cand[mid]) < b) { ans = cand[mid]; hi = mid - 1; } // ⚠ 少了一个等号 else lo = mid + 1; } if (ans < 0) cout << "AFK\n"; else cout << ans << '\n'; return 0;}点「运行 ▶」看结果
题面:「如果他的血量降低至负数,则他就无法到达奥格瑞玛。」
⇒ 掉血正好等于 b 时,血量是 0,0 不是负数 ⇒ 这条路是可行的。判据必须是 dist <= b。
| 300 轮 | 顺手随机(b 从 1~40 里抽) |
★ 把 b 对齐到一条真实路径的长度 |
|---|---|---|
「< 而不是 <=」被抓 |
9 | ★ 300 |
那一档的做法只有一句:先跑一次不限阈值的最短路,把 b 设成它
—— 于是「掉血恰好等于 b」这条线必然被踩到。
⇒ ★★ 又一次「边界要跨过那条具体的线」: 这条线不是「大概在附近」,是恰好那一个值 —— 而顺手随机撞上它的概率就是 9/300。 ★ 这也是本书那条老规矩的现场:抓不到时别加轮数,去想那条线在哪儿、然后照着它造。 (加轮数能把 9/300 变成 90/3000,但改一个档位直接就是 300/300。)
4★★★ 第三个错法:忘了「双向公路」—— 同一行代码,这一章演了三遍
// ✗ 错法③:忘了「双向公路」,每条边只存一遍//// 题面:「城市之间有 m 条**双向**的公路」。//// ⇒ ★★★ 这一章的题单把同一行代码演了**三遍,而且结论各不相同**:// [P1339](/sol/p1339/)(无向图):`g[v].push_back(...)` —— **必须写**;// [P1629](/sol/p1629/)(单行道):写了它**当场就错**;// P1462(双向公路):**又必须写**。// ⇒ 「存一遍还是两遍」不是一个能背的习惯,是**每道题读一次题面**的事// (第 52 章那条「上一章的正确写法就是这一章的 bug」,在同一张题单里演到了第三次)。//// ★ 它的方向可判:边少了一半 ⇒ 路只会更长、可行的阈值只会更少 ⇒ **答案恒 ≥ 正解**// (走不通就是 AFK,那是「最大」的那一端)。
#include <bits/stdc++.h>using namespace std;typedef long long ll;typedef pair<ll, int> PLI;
const ll INF = (ll)4e18;
int n, m;ll b;vector<int> f;vector<vector<pair<int, int>>> g;
static ll shortest(int lim) { if (f[1] > lim || f[n] > lim) return INF; vector<ll> dist(n + 1, INF); priority_queue<PLI, vector<PLI>, greater<PLI>> q; dist[1] = 0; q.push({0, 1}); while (!q.empty()) { auto [d, u] = q.top(); q.pop(); if (d > dist[u]) continue; for (auto [v, w] : g[u]) { if (f[v] > lim) continue; if (d + w < dist[v]) { dist[v] = d + w; q.push({dist[v], v}); } } } return dist[n];}
int main() { ios::sync_with_stdio(false); cin.tie(nullptr); if (!(cin >> n >> m >> b)) return 0; f.assign(n + 1, 0); for (int i = 1; i <= n; i++) cin >> f[i]; g.assign(n + 1, {}); for (int i = 0; i < m; i++) { int u, v, w; cin >> u >> v >> w; g[u].push_back({v, w}); // ⚠ 只存了一遍 } vector<int> cand(f.begin() + 1, f.end()); sort(cand.begin(), cand.end()); cand.erase(unique(cand.begin(), cand.end()), cand.end()); int lo = 0, hi = (int)cand.size() - 1, ans = -1; while (lo <= hi) { int mid = (lo + hi) / 2; if (shortest(cand[mid]) <= b) { ans = cand[mid]; hi = mid - 1; } else lo = mid + 1; } if (ans < 0) cout << "AFK\n"; else cout << ans << '\n'; return 0;}点「运行 ▶」看结果
| 题 | 题面怎么说 | g[v].push_back(...) 那一行 |
|---|---|---|
| P1339 | 无向图 | ★ 必须写 |
| P1629 | 「所有的道路都是单行的」 | ★ 写了当场就错 |
| P1462(这道题) | 「m 条双向的公路」 |
★ 又必须写 |
⇒ 它不是一个能背的习惯,是每道题读一次题面的事。 本书那条「上一章的正确写法就是这一章的 bug」,在同一张题单里演到了第三次。
| 300 轮 | |
|---|---|
| 被抓 | 42 |
| 其中答案比正解小 | ★ 0 |
★ 方向同样可判:边少了一半 ⇒ 路只会更长、可行的阈值只会更少 ⇒ 答案恒 ≥ 正解
(走不通就是 AFK,那是「最大」的那一端)。
⚠ 而它只被抓 42 / 300 —— 因为这道题的图本来就连通,反着走的边多半不在最短路上。
5⚠⚠ 第四个版本:dist 用 int —— 我的草稿在这儿翻了车
// ★ 「掉血用 int、INF 写 0x3f3f3f3f」—— ⚠⚠ 我的草稿说它会溢出,**实测它恰好是对的**//// 草稿里的理由听着很顺:一条路径最多 n−1 = 9999 条边、每条 c ≤ 10⁹// ⇒ 掉血总和能到 10¹³,int 只到 2 147 483 647 —— 差 4657 倍,必炸。//// **这个推理错在「哪一个量真的会被算出来」。** 两行就能证它安全://// ① 只有 `nd < dist[v]` 时才写进 dist,而 dist 的初值是 INF = 0x3f3f3f3f// ⇒ **dist 里存下来的值永远 < 1 061 109 567**;// ② 于是每一次加法都是「一个 < 1 061 109 567 的数」+「一条 ≤ 10⁹ 的边」// ⇒ 最大 **2 061 109 566** < 2 147 483 647。★ **余量 4.0%,一次都不会溢出。**//// ★ 而「答案会不会因此错」也是同一条推理:题面 b ≤ 10⁹,// 所以任何**可行**路径的掉血 ≤ 10⁹ < 0x3f3f3f3f —— 有用的距离一个都没被 INF 拦住。//// ⇒ 这是「[答案 ≥ 任何一个被用到的中间值](/sol/p1164/)」那条论证模式在本章的第三次,// 而这一章的三道题把它的三种结局凑齐了:// [P3371] Σw < 2³¹ ⇒ **不够**(差 2.02 倍,300 / 300 全错)// [P4779] Σw ≤ 10⁹ ⇒ **恰好够**(余量 6.1%)// P1462 b ≤ 10⁹ ⇒ **恰好够**(余量 4.0%)// ⇒ ★★ **「要不要 long long」永远是一道拿这道题的题面乘一遍的算术题,没有通用答案。**//// ⚠ 但结论不是「就该写 int」:4.0% 的余量意味着题面改一个字它就塌了。// 正解仍然用 long long —— **不必去记那 4.0%。**
#include <bits/stdc++.h>using namespace std;typedef pair<int, int> PII;
const int INF = 0x3f3f3f3f;
int n, m;long long b;vector<int> f;vector<vector<PII>> g;
static int shortest(int lim) { if (f[1] > lim || f[n] > lim) return INF; vector<int> dist(n + 1, INF); priority_queue<PII, vector<PII>, greater<PII>> q; dist[1] = 0; q.push({0, 1}); while (!q.empty()) { auto [d, u] = q.top(); q.pop(); if (d > dist[u]) continue; for (auto [v, w] : g[u]) { if (f[v] > lim) continue; if (d + w < dist[v]) { dist[v] = d + w; q.push({dist[v], v}); } } } return dist[n];}
int main() { ios::sync_with_stdio(false); cin.tie(nullptr); if (!(cin >> n >> m >> b)) return 0; f.assign(n + 1, 0); for (int i = 1; i <= n; i++) cin >> f[i]; g.assign(n + 1, {}); for (int i = 0; i < m; i++) { int u, v, w; cin >> u >> v >> w; g[u].push_back({v, w}); g[v].push_back({u, w}); } vector<int> cand(f.begin() + 1, f.end()); sort(cand.begin(), cand.end()); cand.erase(unique(cand.begin(), cand.end()), cand.end()); int lo = 0, hi = (int)cand.size() - 1, ans = -1; while (lo <= hi) { int mid = (lo + hi) / 2; if (shortest(cand[mid]) <= b) { ans = cand[mid]; hi = mid - 1; } else lo = mid + 1; } if (ans < 0) cout << "AFK\n"; else cout << ans << '\n'; return 0;}点「运行 ▶」看结果
草稿里我写的是「必炸」,理由听着很顺:
一条路径最多
n − 1 = 9999条边、每条c ≤ 10⁹⇒ 掉血总和能到 10¹³, 而int只到 2 147 483 647 —— 差 4657 倍。
这个推理错在「哪一个量真的会被算出来」。 两行就证完了:
① dist 里存得下的最大值(初值就是 INF) |
0x3f3f3f3f − 1 = 1 061 109 566 |
② 于是每一次加法最大是它 + 一条最长的边 10⁹ |
2 061 109 566 |
int 的上限 |
2 147 483 647 |
| ⇒ 余量 | ★ 4.0%,一次都不会溢出 |
而「答案会不会因此错」是同一条推理:题面 b ≤ 10⁹,
所以任何可行路径的掉血 ≤ 10⁹ < 0x3f3f3f3f —— 有用的距离一个都没被 INF 拦住。
实测 300 轮,它和正解逐字节相同。
| 题面那句话 | 0x3f3f3f3f 够不够 | 实测 | |
|---|---|---|---|
| P3371 | Σw < 2³¹ |
★ 不够(差 2.02 倍) | 大权值档 300 / 300 全错 |
| P4779 | Σw ≤ 10⁹ |
★ 恰好够(余量 6.1%) | 0 次错 |
| P1462(这道题) | b ≤ 10⁹ |
★ 恰好够(余量 4.0%) | 0 次错 |
⇒ ★★ 「要不要 long long / INF 该写多大」永远是一道拿这道题的题面乘一遍的算术题, 没有通用答案。(这是「答案 ≥ 任何一个被用到的中间值」那条论证模式的第五次登场。)
⚠ 但结论不是「就该写 int」:4.0% 的余量意味着题面改一个字它就塌了。
正解仍然用 long long —— 不必去记那 4.0%。
6★ 对拍这一页
参照物是枚举所有简单路径:既不二分、也不跑最短路,直接按题面的定义走遍每一条路。
300 轮(n 随机 4~7) |
顺手随机 | 换一档 |
|---|---|---|
| 正解 ≡ 枚举所有路径 | ★ 不一致 0 轮 | —— |
| 忘了起点终点 | 179(★ 恒 ≤ 正解) | f 全相同档 ★ 0 |
血量判据写成 < |
9 | ★ b 对齐档 300 |
| 只存一遍边 | 42(★ 恒 ≥ 正解) | —— |
dist 用 int |
★ 0 | 大权值档 ★ 0 |
⚠ 这一档答案就是 AFK 的轮数 |
91 | —— |
又一次那条老规矩:「一致」有两种,都算对了和都没算。
这一档有近三分之一的轮次答案是 AFK —— 在那些轮里,
「忘了两头」「判据写成 <」这些错法很可能也跟着输出 AFK 而蒙混过关。
⇒ 报抓获率之前,先看看这一档到底有多少轮问得出你想问的问题。
7度量程序和生成器
8一页纸
| ★ 关键的一步 | 「最大值的最小值」⇒ 二分那个最大值,固定之后就是本章那一问 |
| ★ 单调性 | 验过:把所有阈值逐个试一遍,300 / 300 轮都是「一串 0 之后全是 1」 |
| ⚠ 忘了起点终点 | 被抓 179(★ 恒 ≤ 正解);f 全相同那档精确的 0;⚠ 这是唯一被样例挡住的 |
★★ 判据写成 < |
「血量降到 0 不是负数」;顺手随机 9 / 300,把 b 对齐到真实路径长度就 300 / 300 |
| ★★★ 忘了双向 | 同一行代码在这张题单里演了三遍:P1339 必须写、P1629 写了就错、这道题又必须写 |
| ⚠⚠ 草稿被打回 | 「dist 用 int 必炸」是错的:dist 被 INF 卡在 1.06×10⁹ 以下 ⇒ 每次加法 ≤ 2 061 109 566,余量 4.0% |