题单 · 习题解析

洛谷 P3385 【模板】负环

★ 问的是「**从 1 出发能到达**的负环」⇒ SPFA / Bellman-Ford 白送、Floyd 要手动补半句;★★★ 「存一遍还是两遍」由**边权的正负号**说了算(一律双向 ⇒ 一条负边自己就是负环,抓 180);★★★ 「从所有点出发」默认档是**精确的 0**(问不出),负环注进「1 走不到那一块」就是 **300 / 300** —— ★ 而它正是[隔壁 P2850](/sol/p2850/) 的**正解**;⚠⚠ 造那一档时本章那招「分两块」**不能照抄**(非负的 U→R 边会被存成 R→U,第一版 776 组全废);★ 两种 cnt 判据**都对**(1500 组 0 不同,能证),记边数的松弛少 1.65 倍

原题:洛谷 P3385出自 第 33 章 最短路二:Floyd、Bellman-Ford、SPFA 与负环 的题单题面本地存档:2026-08-31
⚠ 先自己写一遍,再往下看

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

0题目原文(本地存了一份)

为什么要在这儿抄一份题面

原题在洛谷上(页头有链接)。别人的网站不归我们管,打不开、改版、题号调整都可能发生。 所以每个解析页都把题面转录一份存在本地,跟着仓库一起进版本库。

转录自洛谷 P3385,日期见页头。两边不一致时信原站。

题目描述

给定一个 n 个点的有向图,请求出图中是否存在从顶点 1 出发能到达的负环。

负环的定义是:一条边权之和为负数的回路。

输入格式

本题单测试点有多组测试数据

输入的第一行是一个整数 T,表示测试数据的组数。对于每组数据的格式如下:

第一行有两个整数,分别表示图的点数 n 和接下来给出边信息的条数 m

接下来 m 行,每行三个整数 u, v, w

  • w ≥ 0,则表示存在一条从 uv 边权为 w 的边,还存在一条从 vu 边权为 w 的边
  • w < 0,则表示存在一条从 uv 边权为 w 的边。

输出格式

对于每组数据,输出一行一个字符串,若所求负环存在,则输出 YES,否则输出 NO

说明/提示

数据规模与约定

对于全部的测试点,保证:

  • 1 ≤ n ≤ 2 × 10³1 ≤ m ≤ 3 × 10³
  • 1 ≤ u, v ≤ n−10⁴ ≤ w ≤ 10⁴
  • 1 ≤ T ≤ 10

提示

请注意,m 不是图的边数。

时限 2 秒,内存 250 MB。

输入输出样例

输入

2
3 4
1 2 2
1 3 4
2 3 1
3 1 -3
3 3
1 2 3
2 3 4
3 1 -8

输出

NO
YES

第一组:3 → 1 那条 -3 的边是单向的,1 → 2 → 3 → 1 绕一圈是 2 + 1 − 3 = 0,不负 ⇒ NO。 第二组:1 → 2 → 3 → 13 + 4 − 8 = −1YES。 ★ 注意两组数据的边数 m 都是「行数」,而真实边数比它多 —— 见第 ⑤ 步。

1★ 关键的一步:这道题问的不是「图里有没有负环」

★ 「从顶点 1 出发能到达」这七个字,本章第 1 步专门讲过它对三种算法的代价

图的角落里躺着一个负环,可 1 号点根本过不去 —— 那这道题的答案是 NO

「只算 1 号点能走到的」这件事
Floyd 手动补一句 d[1][k] < INF(它算的是全图,天生不知道 1 号是谁)
Bellman-Ford / SPFA 白送 —— dist 从 1 号点初始化,走不到的点永远是 INF

这道题天生适合 SPFA / Bellman-Ford,而它俩判负环也是白送的:

  • Bellman-Ford:跑完 n−1 轮,第 n 轮还能松弛成功就有负环;
  • SPFA:cnt[v] 记「v 的最短路用了几条边」,cnt[v] ≥ n 就有负环 (n 个点的简单路最多 n−1 条边,用到 n 条必然重复经过某个点、也就是绕了个环, 而绕它让距离变小 —— 那就是负环)。
p3385.cpp★ 这一版就能 AC(SPFA + cnt,顶格 0.11 秒)
// 洛谷 P3385 【模板】负环 —— ★ 这一版就能 AC
//
// 问的是「**从顶点 1 出发能到达**的负环」——正是本章第 1 步那件事,
// 而 SPFA / Bellman-Ford **天生只报告 s 能走到的负环**(dist 从 s 初始化)。
//
// 三处这道题独有的细节:
// ① 读边:w ≥ 0 存双向,w < 0 只存 u → v —— **正负号说了算**
// ② cnt[v] 记的是「最短路用了几条边」,≥ n 就有负环(n 个点的简单路最多 n−1 条边)
// ③ 多组数据,每组都要把 g / dist / cnt / inq 清干净
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while (T--) {
int n, m;
cin >> n >> m;
vector<vector<pair<int, int>>> g(n + 1);
for (int e = 0; e < m; e++) {
int u, v, w;
cin >> u >> v >> w;
g[u].push_back({v, w});
if (w >= 0) g[v].push_back({u, w}); // ★ 正负号说了算
}
const int INF = 0x3f3f3f3f;
vector<int> dist(n + 1, INF), cnt(n + 1, 0);
vector<char> inq(n + 1, 0);
queue<int> q;
dist[1] = 0;
inq[1] = 1;
q.push(1);
bool neg = false;
while (!q.empty() && !neg) {
int u = q.front();
q.pop();
inq[u] = 0; // ★ 出队就清(本章第 7 步)
for (auto [v, w] : g[u])
if (dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
cnt[v] = cnt[u] + 1; // ★ 记的是「几条边」,不是「入队几次」
if (cnt[v] >= n) { neg = true; break; }
if (!inq[v]) { inq[v] = 1; q.push(v); }
}
}
cout << (neg ? "YES" : "NO") << "\n";
}
return 0;
}
点一下即可编辑
输入(stdin)
输出
点「运行 ▶」看结果

2★★★ 第一个错法:读边时不看正负号

p3385Undir.cpp✗ 一律存两遍(样例第一组就打错:YES,答案是 NO)
// ✗ 错法①:边一律存两遍 —— ★★★ 这一页的主线
//
// 「无向图存两遍」是练了二十道题练出来的手感,而这道题的题面把规则改了:
// 若 w ≥ 0,存 u→v 和 v→u;
// 若 w < 0,**只存 u→v**。
//
// 一律存双向的后果是灾难性的:一条 w < 0 的边被存成来回两条,
// 那 u → v → u 绕一圈就是 2w < 0 —— **它自己就是一个负环**。
// ⇒ 只要输入里有一条从 1 号点走得到的负权边,它就无脑输出 YES。
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while (T--) {
int n, m;
cin >> n >> m;
vector<vector<pair<int, int>>> g(n + 1);
for (int e = 0; e < m; e++) {
int u, v, w;
cin >> u >> v >> w;
g[u].push_back({v, w});
g[v].push_back({u, w}); // ✗ 不看正负号,一律两遍
}
const int INF = 0x3f3f3f3f;
vector<int> dist(n + 1, INF), cnt(n + 1, 0);
vector<char> inq(n + 1, 0);
queue<int> q;
dist[1] = 0; inq[1] = 1; q.push(1);
bool neg = false;
while (!q.empty() && !neg) {
int u = q.front(); q.pop(); inq[u] = 0;
for (auto [v, w] : g[u])
if (dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
cnt[v] = cnt[u] + 1;
if (cnt[v] >= n) { neg = true; break; }
if (!inq[v]) { inq[v] = 1; q.push(v); }
}
}
cout << (neg ? "YES" : "NO") << "\n";
}
return 0;
}
点一下即可编辑
输入(stdin)
输出
点「运行 ▶」看结果
★★★ 「存一遍还是两遍」在这道题上换了主语 —— 由 w 的正负号说了算

题面把规则写得明明白白:

w >= 0  ->  存 u->v 和 v->u
w <  0  ->  只存 u->v

一律存两遍的后果是灾难性的:一条 w < 0 的边被存成来回两条, 那 u → v → u 绕一圈就是 2w < 0 —— 它自己就是一个负环。 ⇒ 只要输入里有一条 1 号点走得到的负权边,它就无脑输出 YES。

谁说了算 那一行
P1339 / P1462 / B3647 题面一句话 必须写
P1629 题面一句话 写了当场就错
P1073 ★ 同一张输入里,第三个数 z 说了算 一半写一半不写
P3385(这道题) ★★ 同一张输入里,边权自己的正负号说了算 一半写一半不写

⇒ 第 52 章那条「上一章的正确写法就是这一章的 bug」的第六次现场 —— 它从来不是一个能背的习惯。

300 个种子(每个 2~3 组数据) 默认档 ★ 全非负边档 ★ 负环只在走不到的地方
「一律存双向」被抓 180 精确的 0 300

★ 「全非负边」那一档是这个 0 的自检:所有边本来就该存双向 ⇒ 这个错法在那一档什么都没改。 ⇒ 又一次「造一档违反 / 抽掉那个条件的数据,同时给 0 做自检、又称出那句话的分量」。

3★★★ 第二个错法:从所有点出发 —— 它是下一道题的正解

p3385All.cpp✗ 所有点 dist 初值 0、全部入队(官方样例照过)
// ✗ 错法②:从**所有点**出发(等价于加一个超级源点)
//
// 这是判「图里有没有负环」的标准写法 —— 而这道题问的是
// 「**从顶点 1 出发能到达**的负环」。
// ⇒ 图的角落里躺着一个 1 号点过不去的负环时,它会多报一个 YES。
//
// ★★★ 而下一道 [P2850] 问的恰恰是「不管从哪出发」——
// 同一段代码,在那道题上是正解,在这道题上是错的。
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while (T--) {
int n, m;
cin >> n >> m;
vector<vector<pair<int, int>>> g(n + 1);
for (int e = 0; e < m; e++) {
int u, v, w;
cin >> u >> v >> w;
g[u].push_back({v, w});
if (w >= 0) g[v].push_back({u, w});
}
vector<int> dist(n + 1, 0), cnt(n + 1, 0); // ✗ 所有点初值 0、全部入队
vector<char> inq(n + 1, 1);
queue<int> q;
for (int i = 1; i <= n; i++) q.push(i);
bool neg = false;
while (!q.empty() && !neg) {
int u = q.front(); q.pop(); inq[u] = 0;
for (auto [v, w] : g[u])
if (dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
cnt[v] = cnt[u] + 1;
if (cnt[v] >= n) { neg = true; break; }
if (!inq[v]) { inq[v] = 1; q.push(v); }
}
}
cout << (neg ? "YES" : "NO") << "\n";
}
return 0;
}
点一下即可编辑
输入(stdin)
输出
点「运行 ▶」看结果

这是判「图里有没有负环」的标准写法(等价于加一个连向所有点、边长 0 的超级源点)。 而这道题问的是「从顶点 1 出发能到达的」。

★★★ 同一段代码,在这道题上是错的,在下一道题上是正解
问的是什么 该怎么初始化
P3385(这道题) 从 1 出发能到达的负环 dist[1] = 0,其余 INF
P2850(同一张题单) 「从某块田地出发」⇒ 全图有没有负环 所有 dist = 0,全部入队

⇒ 这就是本章自测最后一条那道思考题的考场版: 「如果题目问的是图里有没有负环(不管从哪出发),三份代码各要改哪里」。 答案是只改初始化那一行,而两道题把这一行的两个取值都考了一遍。

300 个种子 默认档 ★★★ 负环只注入「1 走不到的那一块」
「从所有点出发」被抓 精确的 0 300 / 300

⚠⚠ 默认档那个 0 是「结构性」的:随机小图上,负环差不多总是 1 号点走得到的 (那一档 776 组里 422 组答案就是 YES)—— 它不是抓不到,是问不出这个问题。 ⇒ 又一次「抓不到时别加轮数,去想那条线在哪儿、然后照着它造」。

⚠⚠ 而造那一档时,本章正文那一招不能照抄 —— 我第一版就栽在这儿

本章第 9 步造「走不到的负环」用的是把点分成两块R(含起点)和 U, 只允许 R→RU→UU→R永远不连 R→U

我照抄了,然后档位 2 的 776 组答案全是 YES —— 一个都没造出来。

真因是这道题的读边规则:一条 w ≥ 0U→R 边要存两遍, 那第二遍就是一条 R→U —— 1 号点当场走进了 U。

⇒ 修法两条:R 块内只用非负权(怎么存都不会有负环)、 跨块的 U→R 强制取负(负边只存单向,堵死回头路)。改完这一档就是 300 / 300。

★★ 教训不是「那一招没用」,是 「生成器的招式也有前提」 —— 本章那一招的前提是「边都是有向的」,而这道题把这个前提拿掉了。

4⚠ 第三个错法:多组数据没清干净

p3385Clear.cpp✗ dist / cnt / inq 提到了循环外面(样例第二组就打错)
// ✗ 错法③:多组数据没清干净
//
// 题面第一句就是「**本题单测试点有多组测试数据**」。
// 这一版把 dist / cnt / inq 提到了循环外面(很多人写惯了全局数组就是这样),
// 于是上一组的 dist 会当成这一组的初值 —— 而上一组可能刚跑出一堆负数。
#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int MAXN = 2005;
int dist_[MAXN], cnt[MAXN];
char inq[MAXN];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
for (int i = 0; i < MAXN; i++) dist_[i] = INF; // ✗ 只在最外面初始化了一次
while (T--) {
int n, m;
cin >> n >> m;
vector<vector<pair<int, int>>> g(n + 1);
for (int e = 0; e < m; e++) {
int u, v, w;
cin >> u >> v >> w;
g[u].push_back({v, w});
if (w >= 0) g[v].push_back({u, w});
}
queue<int> q;
dist_[1] = 0; inq[1] = 1; q.push(1);
bool neg = false;
while (!q.empty() && !neg) {
int u = q.front(); q.pop(); inq[u] = 0;
for (auto [v, w] : g[u])
if (dist_[u] + w < dist_[v]) {
dist_[v] = dist_[u] + w;
cnt[v] = cnt[u] + 1;
if (cnt[v] >= n) { neg = true; break; }
if (!inq[v]) { inq[v] = 1; q.push(v); }
}
}
cout << (neg ? "YES" : "NO") << "\n";
}
return 0;
}
点一下即可编辑
输入(stdin)
输出
点「运行 ▶」看结果

题面第一句就是「本题单测试点有多组测试数据」。 写惯了全局数组的人很容易把初始化也留在最外面,于是上一组跑出来的一堆负 dist 成了这一组的初值

300 个种子 默认档 ★ 「第一组必有负环」档 全非负边档
「没清干净」被抓 136 66 0

⚠⚠ 这一档是我造反了的:专门让第一组带负环,本以为残留会更狠,实测反而只有一半。 道理事后很清楚:默认档里后面几组也常常带负环,一路残留下去污染得更彻底; 而「只有第一组有」意味着后面几组是干净的正权图,残留的负 dist 反而常常被正权边盖回去。

★ 这是「拧一个旋钮之前,先量一下它到底控制着什么」的又一次 —— 「专门为这个 bug 造的档」不一定比默认档强,这句话只能量。

5⚠ 题面「提示」那一节只有一句话,而它是一句算术

请注意,m 不是图的边数。

p3385Small.cpp✗ 链式前向星的边数组按 m 开(顶格档打 OVERFLOW)
// ✗ 错法⑤:链式前向星的边数组**按 m 开**
//
// 题面「提示」那一节只有一句话:**「请注意,m 不是图的边数。」**
// 它说的就是这件事:w ≥ 0 的那些行**各要存两条**边 ⇒ 真实边数最多 2m = 6000。
//
// ⚠ 数组开小是未定义行为(越界写别人的内存),演示不出可复现的结果 ——
// 所以这一版**加了一个出口**:一旦要写的下标越过数组,就打 `OVERFLOW` 退出。
// 真交上去的话,那儿是一个安静的越界写。
#include <bits/stdc++.h>
using namespace std;
const int MAXM = 3005; // ✗ 按题面的 m ≤ 3000 开
int head_[2005], nxt[MAXM], to_[MAXM], wt[MAXM], tot;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while (T--) {
int n, m;
cin >> n >> m;
tot = 0;
for (int i = 0; i <= n; i++) head_[i] = -1;
auto add = [&](int u, int v, int w) {
if (tot >= MAXM) { cout << "OVERFLOW\n"; exit(0); }
to_[tot] = v; wt[tot] = w; nxt[tot] = head_[u]; head_[u] = tot++;
};
for (int e = 0; e < m; e++) {
int u, v, w;
cin >> u >> v >> w;
add(u, v, w);
if (w >= 0) add(v, u, w);
}
const int INF = 0x3f3f3f3f;
vector<int> dist(n + 1, INF), cnt(n + 1, 0);
vector<char> inq(n + 1, 0);
queue<int> q;
dist[1] = 0; inq[1] = 1; q.push(1);
bool neg = false;
while (!q.empty() && !neg) {
int u = q.front(); q.pop(); inq[u] = 0;
for (int i = head_[u]; i != -1; i = nxt[i]) {
int v = to_[i], w = wt[i];
if (dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
cnt[v] = cnt[u] + 1;
if (cnt[v] >= n) { neg = true; break; }
if (!inq[v]) { inq[v] = 1; q.push(v); }
}
}
}
cout << (neg ? "YES" : "NO") << "\n";
}
return 0;
}
点一下即可编辑
输入(stdin)
输出
点「运行 ▶」看结果
题面给的 m 3 000(行数
真实边数上界 2m = ★ 6 000w ≥ 0 的每一行都要存两条)

⚠ 数组开小是未定义行为(安静地越界写别人的内存),演示不出可复现的结果 —— 所以这一版加了一个出口:要写的下标越过数组就打 OVERFLOW 退出。 真交上去的话,那儿是一个不会报错的越界写。 (这一招第 30 章 B3625 用过一次:演示会死循环的写法,得先给它一个出口。)

★ 顺带把「要不要 long long」也算完:dist 最低跌到 −(n−1) × 10⁴ = −19 990 000(发现负环就立刻停,不会一路跌下去), int 的上限是 2 147 483 647 ⇒ 余量 107 倍,不用 long long。

6★ 两种 cnt 判据 —— 而这一个「看着像 bug」的其实是对的

p3385Inq.cpp?cnt 记「入队几次」而不是「最短路几条边」—— 实测它是对的
// ?错法④:cnt 记「入队几次」而不是「最短路用了几条边」
//
// 这是流传很广的另一种写法。它到底对不对,这一页量了一遍 —— 结论写在页面上。
// (本书那条老规矩:**流传很广的提醒,量完之后可能要反过来说。**)
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while (T--) {
int n, m;
cin >> n >> m;
vector<vector<pair<int, int>>> g(n + 1);
for (int e = 0; e < m; e++) {
int u, v, w;
cin >> u >> v >> w;
g[u].push_back({v, w});
if (w >= 0) g[v].push_back({u, w});
}
const int INF = 0x3f3f3f3f;
vector<int> dist(n + 1, INF), cnt(n + 1, 0);
vector<char> inq(n + 1, 0);
queue<int> q;
dist[1] = 0; inq[1] = 1; q.push(1); cnt[1] = 1;
bool neg = false;
while (!q.empty() && !neg) {
int u = q.front(); q.pop(); inq[u] = 0;
for (auto [v, w] : g[u])
if (dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
if (!inq[v]) {
inq[v] = 1;
q.push(v);
if (++cnt[v] >= n) { neg = true; break; } // ?数的是入队次数
}
}
}
cout << (neg ? "YES" : "NO") << "\n";
}
return 0;
}
点一下即可编辑
输入(stdin)
输出
点「运行 ▶」看结果

流传的写法有两种:cnt[v] 记「v 的最短路用了几条边」,或者记「v 入过几次队」。 草稿里我把后者当成错法,实测五个档位、1500 组数据,一次都没和正解不同

★ 它凭什么对,两句话;而它的代价是多做 65% 的松弛

没有负环时,每次成功松弛都让 dist[v] 严格变小,而 dist[v] 只能取「最多走 k 条边」 这 n 个值里的一个 ⇒ 一个点最多入队 n−1 次。 所以「入队 ≥ n 次」同样是负环的充分必要条件。

200 张图(一半带负环) 记「几条边」 记「入队几次」
结论一致 200 / 200 ——
松弛尝试次数 191 024 315 7261.65 倍

⇒ 两个都对,但记边数的那个发现得更早cnt[v] = cnt[u] + 1 是顺着路径一步一步累加的, 一条负环转不了几圈就越过 n;而入队计数要那个点真的被反复弹进弹出。

★ 这是本书那条老规矩的又一次:流传很广的说法,量完之后可能要改的是「它错在哪」, 而不是「它错没错」。第 10 章 P1068 那条的同款。)

⚠ 而「1500 组一次没错」这句话是配了自检才敢写的:这一版在官方样例第二组、 以及默认档 422 组答案为 YES 的数据上都真的打出了 YES ⇒ 这段判负环的代码是活的, 不是一个从不触发的空壳(第 19 章 P2240 立的那条规矩)。

7★ 对拍这一页:参照物走的是 Floyd

p3385Floyd.cpp参照物:Floyd 判负环(d[k][k] < 0 && d[1][k] < INF)
p3385Bell.cpp第二个正解:Bellman-Ford(n−1 轮 + 第 n 轮)

Floyd 和 SPFA 一个字都不共享(没有队列、没有 inq、没有 cnt),所以它是最合适的参照物。 ⚠ 而它只能当参照物,不能当解法n = 2000 ⇒ 三重循环 8 × 10⁹ 次, 本机一组数据就要 2.21 秒,而题面 T 可以到 10、时限只有 2 秒。 (P1332 那条:参照物和解法本来就是两回事。

300 个种子 默认档 ★ 恒 YES 档 ★★★ 负环只在走不到处 全非负档 第一组必有负环
SPFA ≡ Floyd 0 0 0 0 0
Bellman-Ford ≡ Floyd 0 0 0 0 0
记「入队几次」≡ Floyd 0 0 0 0 0
一律存双向 180 ⚠ 0 300 0(自检) 227
从所有点出发 0 ⚠ 0 300 0 2
多组没清干净 136 286 0 0 66
⚠ 这一档答案为 YES 的组数 422 / 776 776 / 776 0 / 776 0 / 776 337 / 776
⚠⚠ 最后那一行是这张表的读法说明:「恒 YES」和「恒 NO」两档都在验零

「负环必在 1 可达处」那一档 776 组全是 YES,于是三个错法里两个一起变成 0 —— 它们全都是「多报 YES」型的,在一个答案本来就是 YES 的档位上,永远露不了馅。

⇒ 这就是那条老规矩的第 N 次:「一致」有两种 —— 都算对了,和都没算。 报抓获率之前先看最后一行:这一档到底有多少组问得出你想问的问题。 (顺带:「全非负边」那一档 0 / 776 全是 NO,它只配当自检档,不能拿来比抓获率。)

8度量程序和生成器

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

9一页纸

★ 关键的一步 问的是「从 1 出发能到达的负环」⇒ SPFA / Bellman-Ford 白送,Floyd 要手动补
★★★ 读边 正负号说了算w ≥ 0 两遍、w < 0 一遍)—— 一律双向 ⇒ 一条负边自己就是负环,抓 180
★★★ 从所有点出发 默认档精确的 0(问不出),负环注进「走不到那一块」就是 300 / 300;★ 而它是下一道题正解
⚠⚠ 生成器 本章那招「分两块」不能照抄 —— 非负的 U→R 边会被存成 R→U,第一版 776 组全废
⚠ 多组数据 没清干净抓 136;⚠ 而「第一组必有负环」那个专门档反而只有 66
⚠ 「m 不是边数」 真实边数上界 2m = 6000;★ dist 最低 −19 990 000 ⇒ 不用 long long
★ 两种 cnt 都对(1500 组 0 不同,能证);记边数的发现更早,松弛少 1.65 倍