题单 · 习题解析

洛谷 P1462 通往奥格瑞玛的道路

★ 「最大值的最小值」⇒ 二分掉,固定阈值之后就是本章那一问(第 9 章二分答案在图上的第一次登场;★ 单调性验过:所有阈值逐个试,300/300 都是「一串 0 之后全是 1」);★★ 「血量降到 0 不是负数」⇒ 判据 `<=`:顺手随机只抓 **9/300**,把 b 对齐到真实路径长度就是 **300/300**;★★★ 「双向公路」让「存一遍还是两遍」在这张题单里演到第三遍(P1339 必须写 / P1629 写了就错 / 这道题又必须写);⚠⚠ 草稿被打回 —— 「dist 用 int 必炸」是错的,**余量 4.0%**

原题:洛谷 P1462出自 第 32 章 最短路一:Dijkstra 的题单题面本地存档:2026-08-30
⚠ 先自己写一遍,再往下看

这一页不是标准答案,是一条阶梯:把大多数人真实会写出的第一版摆出来, 看它错在哪、慢在哪,再一步一步改。所以它对「已经自己动手撞过一次墙」的人最有用。
还没写过的话,先去写 —— 写出来的哪怕是错的,也比直接看这一页收获大。

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 ≤ 200m ≤ 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★ 关键的一步:把「最大值的最小值」二分掉

★ 固定一个上限 mid 之后,题目当场塌成本章那一问

只准走过路费 f[i] ≤ mid 的城市,问 1 → n 的最短(掉血)路是不是 ≤ b。

—— 而这一问,正是本章的 Dijkstra(边权是掉血 c,点权 f 只用来决定哪些点能走)。

二分 + DijkstraO(m log n × log V)。 这是第 9 章那套二分答案在图上的第一次登场。

★ 答案一定是某个 f[i](它就是路径上某个点的费用),所以二分在排序去重后的 f 值上做, 一次都不用去猜值域。

p1462.cpp★ 这一版就能 AC(二分 + Dijkstra)
// 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;
}
点一下即可编辑
输入(stdin)
输出
点「运行 ▶」看结果
★ 二分的前提是单调 —— 而这句话验过了,没有默认

mid 越大,能走的城市只多不少 ⇒ 最短掉血只会更小 ⇒ 「可行」一旦成立就一直成立。

度量程序把所有候选阈值逐个试了一遍(不是二分,是从小到大全跑), 看可行性是不是「一串 0 之后全是 1」:300 / 300 轮都是,没有一轮跳来跳去。

第 9 章那条单调性是二分能用的全部理由,它要验,不能默认。

2⚠ 第一个错法:忘了「过路费包括起点和终点」

p1462NoEnd.cpp✗ 只限制中间的城市(样例打 5,答案是 10)
// ✗ 错法①:只限制**中间**的城市,忘了「过路费包括起点和终点」
//
// 题面原话:「每次经过一个城市,都会被收取一定的过路费(**包括起点和终点**)。」
// 那半句括号是出题人专门加的,而顺手写 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;
}
点一下即可编辑
输入(stdin)
输出
点「运行 ▶」看结果

题面那半句括号「(包括起点和终点)」是出题人专门加的, 而顺手写 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★★ 第二个错法:血量判据写成 `<` —— 一条只有一个点宽的线

p1462Strict.cpp✗ dist < b(少了一个等号,样例照过)
// ✗ 错法②:血量判据写成 `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;
}
点一下即可编辑
输入(stdin)
输出
点「运行 ▶」看结果

题面:「如果他的血量降低至负数,则他就无法到达奥格瑞玛。」

⇒ 掉血正好等于 b 时,血量是 00 不是负数 ⇒ 这条路是可行的。判据必须是 dist <= b

★★ 顺手随机 9 / 300,把 b 对齐到真实路径长度就是 300 / 300
300 轮 顺手随机(b 从 1~40 里抽) ★ 把 b 对齐到一条真实路径的长度
< 而不是 <=」被抓 9 300

那一档的做法只有一句:先跑一次不限阈值的最短路,把 b 设成它 —— 于是「掉血恰好等于 b」这条线必然被踩到。

⇒ ★★ 又一次「边界要跨过那条具体的线」: 这条线不是「大概在附近」,是恰好那一个值 —— 而顺手随机撞上它的概率就是 9/300。 ★ 这也是本书那条老规矩的现场:抓不到时别加轮数,去想那条线在哪儿、然后照着它造。 (加轮数能把 9/300 变成 90/3000,但改一个档位直接就是 300/300。)

4★★★ 第三个错法:忘了「双向公路」—— 同一行代码,这一章演了三遍

p1462Dir.cpp✗ 每条边只存一遍(样例照样打 10)
// ✗ 错法③:忘了「双向公路」,每条边只存一遍
//
// 题面:「城市之间有 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;
}
点一下即可编辑
输入(stdin)
输出
点「运行 ▶」看结果
★★★ 「存一遍还是两遍」在这一张题单里出现了三次,三次答案都不同
题面怎么说 g[v].push_back(...) 那一行
P1339 无向图 必须写
P1629 「所有的道路都是单行的」 写了当场就错
P1462(这道题) m双向的公路」 又必须写

它不是一个能背的习惯,是每道题读一次题面的事。 本书那条「上一章的正确写法就是这一章的 bug」,在同一张题单里演到了第三次。

300 轮
被抓 42
其中答案比正解小 0

★ 方向同样可判:边少了一半 ⇒ 路只会更长、可行的阈值只会更少 ⇒ 答案恒 ≥ 正解 (走不通就是 AFK,那是「最大」的那一端)。 ⚠ 而它只被抓 42 / 300 —— 因为这道题的图本来就连通,反着走的边多半不在最短路上。

5⚠⚠ 第四个版本:dist 用 int —— 我的草稿在这儿翻了车

p1462Int.cpp★ int + 0x3f3f3f3f —— 实测它是对的
// ★ 「掉血用 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;
}
点一下即可编辑
输入(stdin)
输出
点「运行 ▶」看结果

草稿里我写的是「必炸」,理由听着很顺:

一条路径最多 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★ 对拍这一页

参照物是枚举所有简单路径:既不二分、也不跑最短路,直接按题面的定义走遍每一条路。

p1462Brute.cpp参照物:枚举所有简单路径(300 轮不一致 0 轮)
300 轮(n 随机 4~7) 顺手随机 换一档
正解 ≡ 枚举所有路径 不一致 0 轮 ——
忘了起点终点 179(★ 恒 ≤ 正解) f 全相同档 ★ 0
血量判据写成 < 9 ★ b 对齐档 300
只存一遍边 42(★ 恒 ≥ 正解) ——
dist 用 int 0 大权值档 ★ 0
⚠ 这一档答案就是 AFK 的轮数 91 ——
⚠ 顺带数一数:91 / 300 轮答案本来就是 AFK

又一次那条老规矩:「一致」有两种,都算对了和都没算。

这一档有近三分之一的轮次答案是 AFK —— 在那些轮里, 「忘了两头」「判据写成 <」这些错法很可能也跟着输出 AFK 而蒙混过关。 ⇒ 报抓获率之前,先看看这一档到底有多少轮问得出你想问的问题。

7度量程序和生成器

p1462Count.cpp度量程序(本页所有数字都出自它)
p1462Gen.cpp(四个档位)数据生成器

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%