0题目原文(本地存了一份)
原题在洛谷上(页头有链接)。别人的网站不归我们管,打不开、改版、题号调整都可能发生。 所以每个解析页都把题面转录一份存在本地,跟着仓库一起进版本库。
转录自洛谷 P3366,日期见页头。两边不一致时信原站。
题目描述
如题,给出一个无向图,求出最小生成树,如果该图不连通,则输出 orz。
输入格式
第一行包含两个整数 N, M,表示该图共有 N 个结点和 M 条无向边。
接下来 M 行每行包含三个整数 Xᵢ, Yᵢ, Zᵢ,表示有一条长度为 Zᵢ 的无向边连接结点 Xᵢ, Yᵢ。
输出格式
如果该图连通,则输出一个整数表示最小生成树的各边的长度之和。如果该图不连通则输出 orz。
说明/提示
数据规模:
对于 20% 的数据,N ≤ 5,M ≤ 20。
对于 40% 的数据,N ≤ 50,M ≤ 2500。
对于 70% 的数据,N ≤ 500,M ≤ 10⁴。
对于 100% 的数据:1 ≤ N ≤ 5000,1 ≤ M ≤ 2 × 10⁵,1 ≤ Zᵢ ≤ 10⁴,1 ≤ Xᵢ, Yᵢ ≤ N。
时限 1 秒,内存 128 MB。
输入输出样例
输入
4 5 1 2 2 1 3 2 1 4 3 2 3 4 3 4 3
输出
7
样例解释(原题的图,本地存了一份):

所以最小生成树的总边权为 2 + 2 + 3 = 7。
1第一版:把本章第 6 步的 Kruskal 照搬过来
这道题是模板题,本章第 6 步那份 kruskal.cpp 几乎可以原样交上去 ——
排序、能连就连、并查集判环,三件事一件不少。
// ✗ P3366 的第一版:忘了「选够 n − 1 条没有」//// 它在**连通**的图上一个字都不错 —— 官方样例照过,顺手写的对拍也照过。// 图一旦不连通,它输出的是「最小生成**森林**」的权和:一个看着完全合理的正整数。// ⇒ 这就是本页第 ③ 步那件事:**错法不输出垃圾,它输出一个像答案的东西。**
#include <bits/stdc++.h>using namespace std;
int fa[5005];int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
struct E { int u, v, w; };
int main() { ios::sync_with_stdio(false); cin.tie(nullptr);
int n, m; cin >> n >> m; vector<E> e(m); for (auto& t : e) cin >> t.u >> t.v >> t.w;
sort(e.begin(), e.end(), [](const E& a, const E& b) { return a.w < b.w; }); for (int i = 1; i <= n; i++) fa[i] = i;
int sum = 0; for (const auto& t : e) { int x = find(t.u), y = find(t.v); if (x == y) continue; fa[x] = y; sum += t.w; } cout << sum << '\n'; // ✗ 从头到尾没数过收了几条 return 0;}点「运行 ▶」看结果
它在官方样例上就是标准答案。 而它是错的 —— 错在题面最后那半句话上。
题面把这半句话说了两遍(描述里一遍、输出格式里一遍)。而上面那一版从头到尾没数过自己收了几条边。
图一旦不连通,Kruskal 的循环照样跑完,它给出的是最小生成森林的权和 —— 一个正的、看着完全合理的整数。
错法不输出垃圾,它输出一个像答案的东西。 这就是它能骗到人的全部原因。
⇒ 补的是一句话:树长成了必然恰好 n − 1 条边,数一数就行。
// P3366【模板】最小生成树 —— ★ 这一版就能 AC//// Kruskal:把边按权排序,能连就连;数够 n − 1 条才算连通,否则输出 orz。//// ★ 这道题真正的考点不是「怎么求 MST」,是最后那一句:**图可能不连通**。// 而顺手写的生成器(先造一棵树保证连通、再撒边)**一条不连通的图都造不出来**// ⇒ 那一档对拍是精确的 0,见 p3366Gen.cpp 的档位 1。//// 答案上界:(n − 1) × Z ≤ 4999 × 10⁴ = 49 990 000 < 2³¹ − 1(余量 43 倍)⇒ **不用 long long**。
#include <bits/stdc++.h>using namespace std;
int fa[5005];int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); } // 路径压缩
struct E { int u, v, w; };
int main() { ios::sync_with_stdio(false); cin.tie(nullptr);
int n, m; cin >> n >> m; vector<E> e(m); for (auto& t : e) cin >> t.u >> t.v >> t.w;
sort(e.begin(), e.end(), [](const E& a, const E& b) { return a.w < b.w; }); for (int i = 1; i <= n; i++) fa[i] = i;
int sum = 0, cnt = 0; for (const auto& t : e) { int x = find(t.u), y = find(t.v); if (x == y) continue; // 同一棵树里再连就成环了 fa[x] = y; sum += t.w; if (++cnt == n - 1) break; // 够 n − 1 条,树已经长成 }
if (cnt == n - 1) cout << sum << '\n'; else cout << "orz\n"; // ⚠ 全小写。题面里那三个字母是 `orz` return 0;}点「运行 ▶」看结果
2★★★ 而顺手写的生成器,一组不连通的图都造不出来
写生成器时最自然的一招,就是先拉一棵树把所有点串起来,再随机撒几条边。 这一招在前面三十几页里用过无数次,因为它省事、而且大多数图论题都要求连通。
可这道题的考点恰恰就是不连通。
| 300 轮 | ★ 档 0:先造树(顺手写的样子) | ★ 档 1:照题面随机撒 m 条边 |
★★ 档 2:保证不连通 |
|---|---|---|---|
「忘了判 n − 1 条」被抓 |
★ 精确的 0 | 208 | 300 |
⇒ 抓不到它的不是轮数,是档位 —— 档 0 加到一万轮仍然是 0,因为那一档的图永远连通。
度量程序独立数了一遍「档 1 那 300 组里,图真的不连通有几组」:
| 档 1 里图真的不连通 | 208 / 300 |
「忘了判 n − 1 条」被抓 |
★ 208 / 300 |
★ 一个不差,而且这次不用量也能证:图连通时它和正解一字不差;
图不连通时正解输出 orz、它输出一个数字 ⇒ 两件事互为充要条件。
⇒ 「触发条件 ≡ 抓获数」这条本书量到过很多次(P2240 239 ≡ 239、 P1094 191 ≡ 191、P1077 三档全等 …), ★ 而能证的等价上一次是 P2865。 ⚠ 别把它当定律 —— 同一件事在 P1803 上差了 60 倍、在 B3637 上差 150 倍。
3⚠ 和算法完全无关的那一条:orz 是三个小写字母
// ✗ P3366:算法一个字没错,只是把 orz 写成了 Orz//// ★ 它是这一页唯一一个「和算法完全无关」的错法,也是唯一一个**对拍逐字节比才抓得到**的。// ⚠ 而它和上一版(忘了判连通)在**同一个档位**才现形 —— 图连通的时候两个都是精确的 0。
#include <bits/stdc++.h>using namespace std;
int fa[5005];int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
struct E { int u, v, w; };
int main() { ios::sync_with_stdio(false); cin.tie(nullptr);
int n, m; cin >> n >> m; vector<E> e(m); for (auto& t : e) cin >> t.u >> t.v >> t.w;
sort(e.begin(), e.end(), [](const E& a, const E& b) { return a.w < b.w; }); for (int i = 1; i <= n; i++) fa[i] = i;
int sum = 0, cnt = 0; for (const auto& t : e) { int x = find(t.u), y = find(t.v); if (x == y) continue; fa[x] = y; sum += t.w; if (++cnt == n - 1) break; }
if (cnt == n - 1) cout << sum << '\n'; else cout << "Orz\n"; // ✗ 题面写的是 orz,全小写 return 0;}点「运行 ▶」看结果
| 300 轮 | 档 0 先造树 | 档 1 随机撒 | 档 2 保证不连通 |
|---|---|---|---|
忘了判 n − 1 条 |
0 | 208 | 300 |
★ orz 打成 Orz |
0 | 208 | 300 |
两行逐格相同:它们都只在「图不连通」那些轮里现形,而那正是 208 / 300 那一档。
⇒ ★★ 这一条只有逐字节比才抓得到 —— 前面很多页都提过「对拍别 strip()」
(P1177 那次一 strip 就漏了 307 轮),这一页是它最干净的形态:
两版的差别只有一个字母的大小写。
4⚠ 并查集:fa[x] 是「爸爸」,不是「祖宗」
// ✗ P3366:并查集不 find,直接比 fa[x] 和 fa[y]//// 这是本章第 6 步点过的那个误解在真题上的价钱:**fa[x] 是「爸爸」,不是「祖宗」**。// ⚠ 而它在官方样例上**打出的就是标准答案 7** —— 三条边一条不多一条不少。// 理由能说清楚:那组数据里被合并的树一直只有两层,爸爸恰好就是祖宗。
#include <bits/stdc++.h>using namespace std;
int fa[5005];
struct E { int u, v, w; };
int main() { ios::sync_with_stdio(false); cin.tie(nullptr);
int n, m; cin >> n >> m; vector<E> e(m); for (auto& t : e) cin >> t.u >> t.v >> t.w;
sort(e.begin(), e.end(), [](const E& a, const E& b) { return a.w < b.w; }); for (int i = 1; i <= n; i++) fa[i] = i;
int sum = 0, cnt = 0; for (const auto& t : e) { if (fa[t.u] == fa[t.v]) continue; // ✗ 比的是爸爸,不是祖宗 fa[t.u] = fa[t.v]; // ✗ 改的也只是自己那一个格子 sum += t.w; if (++cnt == n - 1) break; }
if (cnt == n - 1) cout << sum << '\n'; else cout << "orz\n"; return 0;}点「运行 ▶」看结果
本章第 6 步专门点过这个误解。这一页给它称重量。
样例的边按权排完是 (1,2,2) (1,3,2) (1,4,3) (3,4,3) (2,3,4)。前三条各连进一个新点,
fa 被改成 fa[1]=2 → fa[1]=3 → fa[1]=4,每一步比的那两个格子恰好就是各自的祖宗,
三条边收完 cnt 就够了 n − 1 = 3,输出 7。
⇒ 又一次那条规律:样例筛掉的是「每组都错」的,放过的是「偶尔才错」的。
它出错要两步:① 出现「两个点真在同一棵树里,可 fa 那一格看不出来」;
② 而且这一步真的改变了最后那个和。
| 档 0 · 300 轮 | |
|---|---|
第一层(fa[u] != fa[v] 但其实同树)至少出现一次 |
99 |
| 真被抓 | 94 |
★ 比值 1.05 倍。⇒ 这条曲线本书已经量到从 1.0 倍(P2240) 一直到 150 倍(B3637)都有 —— 只能量,不能推。
5★★★ 排序反了:说清楚它「算了什么」
// ✗ P3366:排序的比较函数写反了(从大到小)//// ★ 本章第 6 步那条恒等式:它不是「算错了」,它**精确地在解最大生成树**// —— 见 p3366Count.cpp 的 maxst 那一行,300 组逐个相等。// ⇒ 说清楚一个 bug「算了什么」之后,它的所有表现都是白送的推论:// 所有边权都相同的图上它是对的,别的图上它每一组都错(⇒ 官方样例当场挡住)。
#include <bits/stdc++.h>using namespace std;
int fa[5005];int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
struct E { int u, v, w; };
int main() { ios::sync_with_stdio(false); cin.tie(nullptr);
int n, m; cin >> n >> m; vector<E> e(m); for (auto& t : e) cin >> t.u >> t.v >> t.w;
sort(e.begin(), e.end(), [](const E& a, const E& b) { return a.w > b.w; }); // ✗ 反了 for (int i = 1; i <= n; i++) fa[i] = i;
int sum = 0, cnt = 0; for (const auto& t : e) { int x = find(t.u), y = find(t.v); if (x == y) continue; fa[x] = y; sum += t.w; if (++cnt == n - 1) break; }
if (cnt == n - 1) cout << sum << '\n'; else cout << "orz\n"; return 0;}点「运行 ▶」看结果
度量程序拿一份独立写的最大生成树(朴素 Prim,取 max 而不是 min,
和 Kruskal 一行代码都不共享)逐组去比:
| 档 0 · 300 组 | |
|---|---|
| 「排序反了」的输出 ≡ 独立算的最大生成树 | ★ 300 / 300 |
⇒ 说清楚它算了什么之后,它的所有表现都是白送的推论:
- 所有边权都相同的图上,最大生成树 = 最小生成树 ⇒ 它是对的(档 3 实测精确的 0);
- 别的图上它几乎每组都错(档 0 抓 296 / 300)⇒ 官方样例当场挡住(10 ≠ 7);
- 图不连通时它照样收不满
n − 1条 ⇒ 也输出orz(下一段那件事)。
★ 这是本章第 6 步那条恒等式在真题上的兑现,也是又一次 「说清楚一个 bug 算了什么,比说它错了有用得多」。
表格里「排序反了」在 档 2(保证不连通) 上是 精确的 0。 差一点就要写成「不连通的图挡住了这个 bug」,而真相是:
| 档 2 · 300 组 | |
|---|---|
正解本来就输出 orz |
★ 300 / 300 |
⇒ 两版一起打出 orz,对拍记「通过」,而验的是零。
★ 这是「一致有两种:都算对了,和都没算」的又一次现场 —— ⚠ 而它和第 28 章 P2704 那次(轮数是假的:全平原时 300 轮只有一组不同的输入) 是两种不同的假:那次是输入退化,这次是答案退化成了常量。
6★★ 答案对,但跑不完 —— 而顶格随机一眼都看不出来
// ⚠ P3366:并查集写对了,只是**没有路径压缩**(find 老老实实往上爬)//// ★★ 这一版的答案**永远是对的** —— 对拍一万轮也抓不到它。// 它坏的不是答案,是复杂度:单次 find 最坏 O(n),而这道题有 2 × 10⁵ 条边。// ⇒ 「答案对但跑不完」只能靠**数次数 + 造对形状**发现([P5019] 那条)。// 造法见 p3366Gen.cpp 档位 4:先用最轻的那批星形边把并查集**拉成一条链**// (`fa[find(x)] = find(y)` 每次都把整棵树挂到一个新点下面),// 再让剩下的十几万条「两头已经在同一棵树里」的边,每一条都从链底爬到链顶。// ⚠ 关键是**最后一条边留到最重** —— 否则树提前长成,那句 break 会救了它。
#include <bits/stdc++.h>using namespace std;
int fa[5005];
int find(int x) { // ⚠ 少的就是 `fa[x] =` 那半句 while (fa[x] != x) x = fa[x]; return x;}
struct E { int u, v, w; };
int main() { ios::sync_with_stdio(false); cin.tie(nullptr);
int n, m; cin >> n >> m; vector<E> e(m); for (auto& t : e) cin >> t.u >> t.v >> t.w;
sort(e.begin(), e.end(), [](const E& a, const E& b) { return a.w < b.w; }); for (int i = 1; i <= n; i++) fa[i] = i;
int sum = 0, cnt = 0; for (const auto& t : e) { int x = find(t.u), y = find(t.v); if (x == y) continue; fa[x] = y; sum += t.w; if (++cnt == n - 1) break; }
if (cnt == n - 1) cout << sum << '\n'; else cout << "orz\n"; return 0;}点「运行 ▶」看结果
find 往上爬了多少步 —— 一把机器无关的尺子:
顶格 n = 5000 / m = 2 × 10⁵ |
没有路径压缩 | 有路径压缩 | 倍数 |
|---|---|---|---|
| ★ 顶格随机(顺手造的那种) | 21 868 994 | 143 687 | 152 倍 |
| ★★ 卡链档(下面那张造法) | ★ 1 961 527 496 | 414 986 | 4726 倍 |
⇒ 同样是顶格,换个形状,朴素版的工作量差 89.7 倍。
秒表印证(A 机 · WSL2 · 8 核 / 7 G · 2026-08-31 · 独占 · 5 次取最小):
| Kruskal(有压缩) | ⚠ 没有路径压缩 | |
|---|---|---|
| 顶格随机 | 30 毫秒 | 50 毫秒 ← 看着完全没问题 |
| ★ 卡链档 | 27 毫秒 | ★ 2842 毫秒(时限 1 秒) |
⇒ ★★★ 顺手造一组顶格随机跑一遍,这个 bug 一眼都看不见 (这已经是第 三 轮栽在同一件事上了: 大不够,还要形状对)。
fa[find(x)] = find(y) 这种写法,每次都把整棵树挂到另一个根下面。
于是喂给它一串星形边 (1,2) (1,3) (1,4) …,并查集会长成一条链:
(1,2) ⇒ fa[1]=2 链:1 → 2
(1,3) ⇒ find(1)=2,fa[2]=3 链:1 → 2 → 3
(1,4) ⇒ find(1)=3,fa[3]=4 链:1 → 2 → 3 → 4链拉好之后,再压十几万条「两头已经在同一棵树里」的边 —— 每一条都要从链底爬到链顶。
⚠⚠ 而这里有一个不留神就白造的地方:Kruskal 收够 n − 1 条就 break。
如果那条「最后一根救命边」排在前面,树提前长成,那句 break 会把朴素版救了。
⇒ 所以生成器把最后那条边的权值设成 10⁴(全场最重),逼它跑完十几万条冗余边。
7★ 另一条路:堆优化 Prim,以及一道该先算再量的账
// ★ P3366 的另一条路:堆优化 Prim —— 它也能 AC//// 本章第 7 步那句话:它和第 32 章的堆优化 Dijkstra **只差一个字**// (Dijkstra 比的是 `dist[u] + w`,Prim 比的就是 `w`)。// ⚠ 而这道题上它**比 Kruskal 慢**:n = 5000 / m = 2 × 10⁵ 是一张**稀疏图**,// Kruskal 那一遍 sort 又快又便宜。实测见本页第 ⑥ 步那张表。
#include <bits/stdc++.h>using namespace std;using pii = pair<int, int>; // ⚠ (边权, 点号) —— 和第 32 章堆里那个顺序一样
int main() { ios::sync_with_stdio(false); cin.tie(nullptr);
int n, m; cin >> n >> m; vector<vector<pii>> g(n + 1); // g[u] 里存 (邻居, 边权) 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<char> in(n + 1, 0); priority_queue<pii, vector<pii>, greater<pii>> q; q.push({0, 1}); // 从 1 号点开始长
int sum = 0, cnt = 0; while (!q.empty()) { auto [w, u] = q.top(); q.pop(); if (in[u]) continue; // 已经在树里了,这条是过期货 in[u] = 1; sum += w; cnt++; for (auto [v, c] : g[u]) if (!in[v]) q.push({c, v}); }
if (cnt == n) cout << sum << '\n'; // ⚠ Prim 数的是**点**,够 n 个才连通 else cout << "orz\n"; return 0;}点「运行 ▶」看结果
本章第 12 步自己写着这句话。这一页就是它的现场。
顶格输入 2 889 103 字节(2.76 MB),光读进来就要一笔钱:
| A 机 · WSL2 · 2026-08-31 · 独占 · 顶格随机 · 5 次取最小 | 端到端 | 减掉读入 |
|---|---|---|
只读入(cin + 关同步) |
19 毫秒 | —— |
| Kruskal | 30 毫秒 | ★ 11 毫秒 |
| 堆优化 Prim | 49 毫秒 | ★ 30 毫秒 |
⇒ 端到端看是 1.6 倍,减掉读入才是 ★ 2.7 倍 —— 两个结论差了将近一倍。
★ 顺带把读入那笔账也做完:不关同步的 cin 要 75 毫秒(比关同步慢 3.9 倍),
光读入就吃掉 7.5% 的时限。
⇒ 这道题四种读法都够,但「关不关同步」这一行值 56 毫秒,顺手写上不亏。
草稿里写的是「n = 5000 ⇒ 邻接矩阵必然爆内存,朴素 Prim 想都别想」。乘一遍就知道那句话不对:
5001 × 5001 × 4 字节 |
95 MB |
| 题面给的内存 | 128 MB |
| ⇒ 余量 | ⚠ 1.34 倍 |
⇒ 它放得下,O(n²) = 2.5 × 10⁷ 也跑得完。正确的说法是
「能过,但余量只有 1.34 倍,而这题 m 只有 2 × 10⁵、本来就是稀疏图」——
⇒ 选 Kruskal 的理由不是「朴素 Prim 会挂」,是没必要冒那个险。
★ 而下一道 P1546 的 n 只有 100,同一张矩阵只要 40 KB ——
「稠密图用朴素 Prim」这句话的主语从来是 n。
8★ 两条一乘就完的算术,和一句噪声
答案上界 (n − 1) × Z |
4999 × 10⁴ = 49 990 000 |
int 上限 |
2 147 483 647 |
| ⇒ 余量 | ★ 42 倍 |
⇒ 这是「答案 ≥ 任何一个被用到的中间值」那条论证模式的又一次 ——
Kruskal 的 sum 只会一路加到答案,中途不会超过它。
前 两 轮连着两次撞上「顺手跳过自环」这件事,所以这一页也量了一遍:
| 档 0 + 档 1,共 600 组 | |
|---|---|
| 输入里有自环 | 367 组 |
「特判 u == v 就 continue」和「不特判」结论不同 |
★ 0 组 |
★ 而这个 0 不是空壳:367 组是真的有自环。理由一行能证 ——
自环的两头 find(u) == find(v) 恒成立,Kruskal 的判环那一句已经把它挡掉了。
⇒ 这是「这句约束重不重要,是「题目 × 你写的那一版」的属性」的又一次:
换成邻接矩阵存图,自环就会占掉 g[u][u] 那一格 —— 同一句话,换个写法就换一个答案。
9★ 对拍这一页
参照物是枚举所有边的子集:挑 n − 1 条边、检查连通、取权和最小的那一套 ——
既不是 Kruskal 也不是 Prim,直接照「生成树」三个字的定义走。
300 轮(n 随机 5~7) |
档 0 先造树 | 档 1 随机撒 | 档 2 保证不连通 | 档 3 边权全相同 |
|---|---|---|---|---|
| Kruskal(正解) | ★ 0 | ★ 0 | ★ 0 | ★ 0 |
| 堆优化 Prim | ★ 0 | ★ 0 | ★ 0 | ★ 0 |
忘了判 n − 1 条 |
★ 0 | 208 | 300 | 0 |
orz 打成 Orz |
★ 0 | 208 | 300 | 0 |
| 比爸爸不比祖宗 | 94 | 89 | 44 | ★ 0 |
| 排序反了 | 296 | 75 | ★ 0 | ★ 0 |
| 没有路径压缩 | ★ 0 | ★ 0 | ★ 0 | ★ 0 |
| 那个 0 | 性质 | 怎么办 |
|---|---|---|
档 0 的「忘了判 n − 1」/「Orz」 |
生成器缺一档(图永远连通) | 换生成器 ⇒ 300 |
| 档 3 的「排序反了」 | ★ 能证的 0(边权全等 ⇒ 最大 = 最小生成树) | 这一档本来就是它的自检 |
| 档 2 的「排序反了」 | ⚠ 在验零(300 轮正解都输出 orz) |
别信它,换档 |
| 档 3 的「比爸爸不比祖宗」 | ★ 能证的 0(边权全等 ⇒ 收哪 n − 1 条,和都一样) |
同上 |
| 「没有路径压缩」四档全 0 | ★★ 它压根不改答案 | 只能数次数 + 造对形状 |
⚠ 而「档 3 全是 0」本身不能自证(「一个反例都没有」和「这段代码没在跑」输出一模一样)—— 这一页的自检是同一张表的另外三列:同一段对拍代码在档 0 / 1 / 2 上抓到了 94 / 208 / 300, 代码是活的。
| 错法 | 抓获率(档 0 / 档 1) | 官方样例挡住了吗 |
|---|---|---|
| 排序反了 | 296 / 300 | ★ 挡住了(打出 10) |
| 比爸爸不比祖宗 | 94 / 89 | ⚠ 放过了(打出 7) |
忘了判 n − 1 条 |
0 / 208 | ⚠ 放过了(样例是连通的) |
orz 打成 Orz |
0 / 208 | ⚠ 放过了(同上) |
| 没有路径压缩 | 0 / 0 | ⚠ 放过了(答案永远对) |
⇒ 五个里只挡住一个,而挡住的正是唯一那个「几乎每组都错」的。 ★ 后面四个各有各的放过理由,而只有第一个是「概率低」,另外三个都是结构性的。
10度量程序和生成器
11一页纸
| ★ 关键的一步 | Kruskal 照抄本章第 6 步;难的是最后那半句「不连通输出 orz」 |
| ★★★ 那个 0 | 「先造树保证连通」的生成器让它变成精确的 0 ⇒ 抓不到它的是档位,不是轮数 |
| ★★ 208 ≡ 208 | 触发条件和抓获数一个不差,而且能证(不连通 ⟺ 正解输出 orz) |
⚠ Orz |
唯一一个和算法无关的错法,抓获数和上一行逐格相同 ⇒ 对拍别 strip |
| ★★★ 排序反了 | 它精确地在解最大生成树(300 / 300)⇒ 所有表现都是白送的推论 |
| ⚠⚠ 档 2 那个 0 | 是假的 —— 那 300 轮正解也输出 orz,在验零 |
| ★★★ 没有路径压缩 | 答案永远对 ⇒ 对拍抓不到;顶格随机 2187 万步(50 毫秒),卡链档 19.6 亿步(2842 毫秒) |
| ★★ 选型 | 端到端 Kruskal 30 ms / 堆优化 Prim 49 ms;★ 减掉 19 ms 的读入才是真差距:11 vs 30 |
| ★ 算术 | int 够(余量 42 倍);⚠ 朴素 Prim 的矩阵 95 MB / 128 MB,没爆,但余量只有 1.34 倍 |