0题目原文(本地存了一份)
原题在洛谷上(页头有链接)。别人的网站不归我们管,打不开、改版、题号调整都可能发生。 所以每个解析页都把题面转录一份存在本地,跟着仓库一起进版本库。
转录自洛谷 P1073,日期见页头。两边不一致时信原站。
题目背景
本题原题数据极弱,Subtask 0 中的测试点为原题测试点,Subtask 1 中的测试点为 Hack 数据。
题目描述
C 国有 n 个大城市和 m 条道路,每条道路连接这 n 个城市中的某两个城市。
任意两个城市之间最多只有一条道路直接相连。
这 m 条道路中有一部分为单向通行的道路,一部分为双向通行的道路,
双向通行的道路在统计条数时也计为 1 条。
C 国幅员辽阔,各地的资源分布情况各不相同,这就导致了同一种商品在不同城市的价格不一定相同。 但是,同一种商品在同一个城市的买入价和卖出价始终是相同的。
商人阿龙来到 C 国旅游。当他得知同一种商品在不同城市的价格可能会不同这一信息之后,
便决定在旅游的同时,利用商品在不同城市中的差价赚回一点旅费。
设 C 国 n 个城市的标号从 1 ~ n,阿龙决定从 1 号城市出发,并最终在 n 号城市结束自己的旅行。
在旅游的过程中,任何城市可以重复经过多次,但不要求经过所有 n 个城市。
阿龙通过这样的贸易方式赚取旅费:他会选择一个经过的城市买入他最喜欢的商品――水晶球,
并在之后经过的另一个城市卖出这个水晶球,用赚取的差价当做旅费。
由于阿龙主要是来 C 国旅游,他决定这个贸易只进行最多一次,
当然,在赚不到差价的情况下他就无需进行贸易。
假设 C 国有 5 个大城市,城市的编号和道路连接情况如下图, 单向箭头表示这条道路为单向通行,双向箭头表示这条道路为双向通行。

假设 1 ~ n 号城市的水晶球价格分别为 4, 3, 5, 6, 1。
阿龙可以选择如下一条线路:1 → 2 → 3 → 5,并在 2 号城市以 3 的价格买入水晶球,
在 3 号城市以 5 的价格卖出水晶球,赚取的旅费数为 2。
阿龙也可以选择如下一条线路:1 → 4 → 5 → 4 → 5,并在第 1 次到达 5 号城市时以 1 的价格买入水晶球,
在第 2 次到达 4 号城市时以 6 的价格卖出水晶球,赚取的旅费数为 5。
现在给出 n 个城市的水晶球价格,m 条道路的信息。请你告诉阿龙,他最多能赚取多少旅费。
输入格式
第一行包含 2 个正整数 n 和 m,分别表示城市的数目和道路的数目。
第二行 n 个正整数,按标号顺序分别表示这 n 个城市的商品价格。
接下来 m 行,每行有 3 个正整数 x, y, z。
如果 z = 1,表示这条道路是城市 x 到城市 y 的单向道路;
如果 z = 2,表示这条道路为城市 x 和城市 y 之间的双向道路。
输出格式
一个整数,表示最多能赚取的旅费。如果没有进行贸易,则输出 0。
说明/提示
【数据范围】
输入数据保证 1 号城市可以到达 n 号城市。
对于 10% 的数据,1 ≤ n ≤ 6。
对于 30% 的数据,1 ≤ n ≤ 100。
对于 50% 的数据,不存在一条旅游路线,可以从一个城市出发,再回到这个城市。
对于 100% 的数据,1 ≤ n ≤ 100000,1 ≤ m ≤ 500000,1 ≤ x, y ≤ n,1 ≤ z ≤ 2。
水晶球价格 ≤ 100。
NOIP 2009 提高组 第三题。时限 1 秒,内存 128 MB。
输入输出样例
输入
5 5 4 3 5 6 1 1 2 1 1 4 1 2 3 2 3 5 1 4 5 2
输出
5
最优是 1 → 4 → 5 → 4 → 5:在 5 号以 1 买、回到 4 号以 6 卖,赚 5。
⚠ 注意这条线路把 4、5 号各走了两遍 —— 这道题的图不是一条路,是可以来回绕的。
1★ 关键的一步(上半):状态不是「在哪儿」,是「在哪儿 + 贸易做到哪一步」
| 层 | 含义 |
|---|---|
| 第 0 层 | 还没买 |
| 第 1 层 | 买了,还没卖 |
| 第 2 层 | 已经卖了 |
- 层内:原图的边,边权 0(走路不花钱);
- 第 0 层的
i→ 第 1 层的i:在 i 买入; - 第 1 层的
i→ 第 2 层的i:在 i 卖出。
答案就是「从 (1, 第 0 层) 走到 (n, 第 2 层)」的最优值。 ★ 「可以重复经过城市」这件事一点额外代价都没有 —— 分层图上它就是普通的走边。
2★★★ 关键的一步(下半):怎么把「求最大利润」变成非负权最短路
顺手的记法是「买入 −p、卖出 +p,求最长路」——
那就不是 Dijkstra 了(有负权,本章第 9 步整节都在讲这件事)。
买入那条边的权 = p[i] (0 ≤ p ≤ 100)
卖出那条边的权 = 100 − p[i] (0 ≤ … ≤ 100)一条完整路线的总权 = 买价 + (100 − 卖价) = 100 − 利润。
⇒ 最小化它就是最大化利润,而所有边权都 ≥ 0 —— 本章的 Dijkstra 原样能用。
★ 那个 100 是从哪儿来的?题面最后一行:「水晶球价格 ≤ 100」。 ⇒ 又一次「题面上那几行数字,每一行都是一件工具」—— 这一行不是背景,它是让这道题落进 Dijkstra 射程的那把钥匙。
// P1073 [NOIP 2009 提高组] 最优贸易 —— ★ 这一版就能 AC(分层图 + 本章的 Dijkstra)//// 题目要的是:从 1 走到 n(点可以重复经过),中途选一个城市买、之后某个城市卖,// 让「卖价 − 买价」最大。//// ★ 关键的一步分两半。//// 【一】**状态是「走到哪儿」+「贸易做到哪一步」** ⇒ 把图复制成三层:// 第 0 层 = 还没买 第 1 层 = 买了还没卖 第 2 层 = 已经卖了// · 层内:原图的边,边权 0(走路不花钱);// · 第 0 层的 i → 第 1 层的 i:在 i 买入;// · 第 1 层的 i → 第 2 层的 i:在 i 卖出。// 答案就是「从 (1, 第 0 层) 走到 (n, 第 2 层)」的最优值。//// 【二】★★★ 而这道题真正的机关是:**怎么把「求最大利润」变成非负权最短路。**// 顺手的做法是买入 −p、卖出 +p 再求最长路 —— 那就**不是 Dijkstra 了**(有负权)。// 换个记法:// 买入那条边的权 = p[i] (0 ≤ p ≤ 100)// 卖出那条边的权 = 100 − p[i] (0 ≤ … ≤ 100)★ 也非负// 于是一条完整路线的总权 = 买价 + (100 − 卖价) = **100 − 利润**。// ⇒ **最小化它就是最大化利润**,而所有边权都 ≥ 0 —— 本章的 Dijkstra 原样能用。//// ★ 那个 100 是从哪儿来的?题面最后一行:「**水晶球价格 ≤ 100**」。// ⇒ 又一次「题面上那几行数字,每一行都是一件工具」——// 这一行不是背景,它是**让这道题落进 Dijkstra 射程**的那把钥匙。//// ⚠ 「不做贸易」也是允许的:在同一个城市买了立刻卖,利润 0;// 分层图里就是 i 的三层顺着走一遍,权 = p + (100 − p) = 100 ⇒ 利润 0。所以答案天然 ≥ 0。//// 复杂度 O(3m log(3n)):顶格 n = 10⁵、m ≈ 5×10⁵ ⇒ 本机 0.28 秒(时限 1 秒;// 而正反两遍那版 0.14 秒 —— 分层图的代价是常数 3,换来的是「状态」这件事说得清)。
#include <bits/stdc++.h>using namespace std;typedef pair<int, int> PII;
const int INF = 0x3f3f3f3f;
int main() { ios::sync_with_stdio(false); cin.tie(nullptr);
int n, m; if (!(cin >> n >> m)) return 0; vector<int> p(n + 1); for (int i = 1; i <= n; i++) cin >> p[i];
int N = 3 * n; // 三层,(层, 点) 编号成 层*n + 点 auto id = [&](int layer, int v) { return layer * n + v; }; vector<vector<PII>> g(N + 1); for (int i = 0; i < m; i++) { int x, y, z; cin >> x >> y >> z; for (int L = 0; L < 3; L++) { // 三层里各连一遍,边权 0 g[id(L, x)].push_back({id(L, y), 0}); if (z == 2) g[id(L, y)].push_back({id(L, x), 0}); } } for (int v = 1; v <= n; v++) { g[id(0, v)].push_back({id(1, v), p[v]}); // ★ 买入:花 p[v] g[id(1, v)].push_back({id(2, v), 100 - p[v]}); // ★ 卖出:花 100 − p[v] }
vector<int> dist(N + 1, INF); priority_queue<PII, vector<PII>, greater<PII>> q; int s = id(0, 1); dist[s] = 0; q.push({0, s}); while (!q.empty()) { auto [d, u] = q.top(); q.pop(); if (d > dist[u]) continue; for (auto [v, w] : g[u]) if (d + w < dist[v]) { dist[v] = d + w; q.push({dist[v], v}); } } int t = dist[id(2, n)]; cout << (t >= INF ? 0 : 100 - t) << '\n'; return 0;}点「运行 ▶」看结果
⚠ 「不做贸易」也是允许的:在同一个城市买了立刻卖,权 = p + (100 − p) = 100 ⇒ 利润 0。
所以答案天然 ≥ 0,不用另写一句 max(0, …)。
本机实测(A 机 · WSL2 · 6.18.33.2-microsoft-standard-WSL2 · 8 线程 / 7 GB ·
2026-08-30 · 独占;./p1073Gen 1 4 100000 500000,顶格 n = 10⁵、m ≈ 5 × 10⁵):
分层图 0.28 秒,正反两遍那版 0.14 秒(时限 1 秒)。
⇒ 分层图的代价是常数 3;换来的是 「状态」这件事说得清 这一点。
3★★★ 第一个错法:题单说的「正反两遍」是对的 —— 可用 Dijkstra 的贪心去做它就错了
题单给的另一条路是「正反两遍最短路」:
minBuy[v] = 从 1 走到 v 的所有路线上,见过的最低价
maxSell[v] = 从 v 走到 n 的所有路线上,见过的最高价
答案 = max over v (maxSell[v] − minBuy[v])
这条路是对的(本页留了一份,300 轮和分层图逐字节相同)。 可它不能用 Dijkstra 的骨架去实现。
本章第 5 步那三句反证,落脚点是一句话:
「那条路走到 x 之后还要往前走,而往前走只会更长(边长非负)。」
⇒ 所以「当前最小的那个」不可能再被刷小,可以当场定死。
而这道题的 minBuy 是「路上的最低价」—— 往前多走一步只会让它更小。
那个单调性正好反过来 ⇒ 「弹出即定死」的理由整个不成立。
// ✗ 错法①:正反两遍是对的,可**用 Dijkstra 的贪心去做它**(弹出即定死)//// ★★★ 这是这一页最值钱的一条:**Dijkstra 的骨架不是随便能套的。**//// 本章第 5 步那三句反证,靠的是一句话:// 「那条路走到 x 之后还要往前走,而**往前走只会更长**(边长非负)。」// ⇒ 所以「当前最小的那个」不可能再被刷小,可以当场定死。//// 而这道题的 minBuy 是「路上的**最低价**」—— **往前多走一步只会让它更小**。// 那个单调性**正好反过来了** ⇒ 弹出即定死的理由整个不成立。//// ⚠ 最小反例只要 4 个点(度量程序第 ③ 段真跑了一遍):// 价格 10 8 1 7,边 1 → 2、2 → 3、3 → 4、1 → 4// · 真正的 minBuy[4] = 1(走 1 → 2 → 3 → 4)// · 贪心:弹出 1(10)⇒ 4 的暂定值 min(10, 7) = 7、2 的暂定值 8// ⇒ 堆顶是 (7, 4),**4 号被带着 7 定死了**,而 3 号(价 1)还没轮到。//// ⚠⚠ 而「minBuy 算错了」推不出「**答案**一定错」—— 这两件事这一页分开量了(正文第 ③ 步)。
#include <bits/stdc++.h>using namespace std;typedef pair<int, int> PII;
int main(int argc, char** argv) { ios::sync_with_stdio(false); cin.tie(nullptr); bool showMin = (argc > 1 && string(argv[1]) == "minbuy"); // 只打 minBuy[],给反例用
int n, m; if (!(cin >> n >> m)) return 0; vector<int> p(n + 1); for (int i = 1; i <= n; i++) cin >> p[i]; vector<vector<int>> g(n + 1), rg(n + 1); for (int i = 0; i < m; i++) { int x, y, z; cin >> x >> y >> z; g[x].push_back(y); rg[y].push_back(x); if (z == 2) { g[y].push_back(x); rg[x].push_back(y); } }
const int INF = 0x3f3f3f3f; /* ⚠ 小根堆 + 「弹出即定死」—— 照搬 Dijkstra 的骨架 */ vector<int> mn(n + 1, INF), done(n + 1, 0); priority_queue<PII, vector<PII>, greater<PII>> q; mn[1] = p[1]; q.push({mn[1], 1}); while (!q.empty()) { auto [d, u] = q.top(); q.pop(); if (done[u]) continue; done[u] = 1; // ⚠ 就是这一句:定死了就不许再改 for (int v : g[u]) { int cand = min(d, p[v]); if (!done[v] && cand < mn[v]) { mn[v] = cand; q.push({cand, v}); } } } if (showMin) { for (int i = 1; i <= n; i++) printf("%d%c", mn[i] >= INF ? -1 : mn[i], " \n"[i == n]); return 0; } /* 反着那一遍同样用大根堆 + 弹出即定死 */ vector<int> mx(n + 1, -1), done2(n + 1, 0); priority_queue<PII> q2; mx[n] = p[n]; q2.push({mx[n], n}); while (!q2.empty()) { auto [d, u] = q2.top(); q2.pop(); if (done2[u]) continue; done2[u] = 1; for (int v : rg[u]) { int cand = max(d, p[v]); if (!done2[v] && cand > mx[v]) { mx[v] = cand; q2.push({cand, v}); } } } int ans = 0; for (int v = 1; v <= n; v++) if (mn[v] < INF && mx[v] >= 0) ans = max(ans, mx[v] - mn[v]); cout << ans << '\n'; return 0;}点「运行 ▶」看结果
最小反例只要 4 个点(价格 10 8 1 7,边 1→2、2→3、3→4、1→4):
minBuy[1] |
[2] |
[3] |
[4] |
|
|---|---|---|---|---|
| 真值(反复松弛) | 10 | 8 | 1 | ★ 1 |
| Dijkstra 的贪心 | 10 | 8 | 1 | ★ 7 |
弹出 1 号(10)之后,4 号的暂定值是 min(10, 7) = 7、2 号是 8
⇒ 堆顶是 (7, 4),4 号就带着 7 被定死了,而价格只有 1 的 3 号还没轮到。
| 300 轮 | 默认档 | 全单向档 | 多环档 | DAG 档 |
|---|---|---|---|---|
| 「用 Dijkstra 的贪心」被抓 | 38 | 32 | 25 | ★ 0 |
上面那个 4 点反例是一张 DAG,它把 minBuy[4] 算错了 ——
可那一轮的最终答案仍然是对的(另一个点上的 maxSell − minBuy 补了回来)。
实测 DAG 档 300 轮,它的答案一次都没错。
⇒ ★★ 这正是第 28 章 P3959 那条: 「它算的是另一个量」推不出「它一定和正解不同」 —— 两个不同的量会在某些输入上取到同一个值,而那个「某些」只能数出来。
4⚠ 第二个错法:假设图无环,按拓扑序 DP —— 而题面替它留了 50 分
// ✗ 错法②:假设这张图**无环**,按拓扑序做一遍 DP//// 想法很自然:从 1 出发的路线是「一步一步往前走」,那就按拓扑序推// minBuy[v] = min(p[v], min over u→v of minBuy[u])// 一遍扫完,`O(n + m)`,比什么都快。//// ⚠ 而题面里那一行就是冲着它写的:// 「**对于 50% 的数据,不存在一条旅游路线,可以从一个城市出发,再回到这个城市。**」// —— 出题人明说了:**有一半的数据是 DAG**。这一版稳拿 50 分。// ⇒ 又一次「[题面的数据范围分档是出题人递过来的工具](/sol/p1141/)」:// 那一档不是提示,是**给这个写法留的分**。//// ⚠ 而题面样例里就有环(`2 3 2` 和 `4 5 2` 都是双向的,双向边本身就是一个二元环)// ⇒ 环上的点进不了拓扑序,这一版直接把它们当成走不到。//// ★ 方向可判:它**少算**了一批路线 ⇒ 利润只会更小 ⇒ **答案恒 ≤ 正解**。
#include <bits/stdc++.h>using namespace std;
int main() { ios::sync_with_stdio(false); cin.tie(nullptr);
int n, m; if (!(cin >> n >> m)) return 0; vector<int> p(n + 1); for (int i = 1; i <= n; i++) cin >> p[i]; vector<vector<int>> g(n + 1), rg(n + 1); vector<int> deg(n + 1, 0), rdeg(n + 1, 0); for (int i = 0; i < m; i++) { int x, y, z; cin >> x >> y >> z; g[x].push_back(y); rg[y].push_back(x); deg[y]++; rdeg[x]++; if (z == 2) { g[y].push_back(x); rg[x].push_back(y); deg[x]++; rdeg[y]++; } }
const int INF = 0x3f3f3f3f; vector<int> mn(n + 1, INF), mx(n + 1, -1); mn[1] = p[1]; { // ⚠ 正着一遍拓扑序 queue<int> q; for (int i = 1; i <= n; i++) if (!deg[i]) q.push(i); while (!q.empty()) { int u = q.front(); q.pop(); for (int v : g[u]) { if (mn[u] < INF) mn[v] = min(mn[v], min(mn[u], p[v])); if (--deg[v] == 0) q.push(v); } } } mx[n] = p[n]; { // ⚠ 反着一遍拓扑序 queue<int> q; for (int i = 1; i <= n; i++) if (!rdeg[i]) q.push(i); while (!q.empty()) { int u = q.front(); q.pop(); for (int v : rg[u]) { if (mx[u] >= 0) mx[v] = max(mx[v], max(mx[u], p[v])); if (--rdeg[v] == 0) q.push(v); } } } int ans = 0; for (int v = 1; v <= n; v++) if (mn[v] < INF && mx[v] >= 0) ans = max(ans, mx[v] - mn[v]); cout << ans << '\n'; return 0;}点「运行 ▶」看结果
「对于 50% 的数据,不存在一条旅游路线,可以从一个城市出发,再回到这个城市。」
—— 出题人明说了:有一半的数据是 DAG。这一版稳拿 50 分。
⇒ 又一次「题面的数据范围分档是出题人递过来的工具」:
那一档不是提示,是给这个写法留的分。
★ 而读的时候要问一句「这一档的主语是谁」—— 别的四档说的是 n,
唯独这一档说的是图的形状。
| 300 轮 | 默认档 | 多环档 | ★ DAG 档 |
|---|---|---|---|
| 「拓扑序 DP」被抓 | 270 | 242 | ★ 精确的 0 |
★ 方向可判:它少算了一批路线 ⇒ 利润只会更小 ⇒ 答案恒 ≤ 正解(300 轮比正解大的是 0)。 ⚠ 而官方样例当场就挡住它(打 0)—— 因为样例里的双向边本身就是一个二元环。
5★★★ 第三个错法:把 z = 2 的双向道路也当成单向
// ✗ 错法③:`z = 2` 那些**双向**道路,只当成 x → y 一个方向//// 题面:「如果 `z = 1`,表示这条道路是城市 x 到城市 y 的单向道路;// 如果 `z = 2`,表示这条道路为城市 x 和城市 y 之间的**双向**道路。」//// ⇒ ★★★ 「存一遍还是两遍」在这一章的题单里出现了**第四次**,而且这次是**同一张输入里两种都有**:// [P1339](/sol/p1339/) 全无向 —— 必须存两遍;// [P1629](/sol/p1629/) 全单向 —— 存两遍就错;// [P1462](/sol/p1462/) 全双向 —— 又必须存两遍;// P1073(这道题)—— ★ **同一行输入里,`z` 说了算。**//// ★ 方向可判:边少了 ⇒ 路线只会更少 ⇒ **答案恒 ≤ 正解**。// ⚠ 而它在「全是单向边」那一档上是**结构性的 0**(那一档根本没有 z = 2)。
#include <bits/stdc++.h>using namespace std;
int main() { ios::sync_with_stdio(false); cin.tie(nullptr);
int n, m; if (!(cin >> n >> m)) return 0; vector<int> p(n + 1); for (int i = 1; i <= n; i++) cin >> p[i]; vector<vector<int>> g(n + 1), rg(n + 1); for (int i = 0; i < m; i++) { int x, y, z; cin >> x >> y >> z; g[x].push_back(y); rg[y].push_back(x); (void)z; // ⚠ z 读进来了,但没人用它 }
const int INF = 0x3f3f3f3f; vector<int> mn(n + 1, INF), mx(n + 1, -1); vector<char> inq(n + 1, 0); deque<int> q; mn[1] = p[1]; q.push_back(1); inq[1] = 1; while (!q.empty()) { int u = q.front(); q.pop_front(); inq[u] = 0; for (int v : g[u]) { int cand = min(mn[u], p[v]); if (cand < mn[v]) { mn[v] = cand; if (!inq[v]) { inq[v] = 1; q.push_back(v); } } } } fill(inq.begin(), inq.end(), 0); mx[n] = p[n]; q.push_back(n); inq[n] = 1; while (!q.empty()) { int u = q.front(); q.pop_front(); inq[u] = 0; for (int v : rg[u]) { int cand = max(mx[u], p[v]); if (cand > mx[v]) { mx[v] = cand; if (!inq[v]) { inq[v] = 1; q.push_back(v); } } } } int ans = 0; for (int v = 1; v <= n; v++) if (mn[v] < INF && mx[v] >= 0) ans = max(ans, mx[v] - mn[v]); cout << ans << '\n'; return 0;}点「运行 ▶」看结果
| 题 | 题面怎么说 | g[v].push_back(...) 那一行 |
|---|---|---|
| P1339 | 无向图 | ★ 必须写 |
| P1629 | 「所有的道路都是单行的」 | ★ 写了当场就错 |
| P1462 | 「m 条双向的公路」 |
★ 又必须写 |
| P1073(这道题) | z = 1 单向、z = 2 双向 |
★★ 同一张输入里,z 说了算 |
⇒ 它从来不是一个能背的习惯,是每道题(这道题是每条边)读一次题面的事。 本书那条「上一章的正确写法就是这一章的 bug」, 在同一张题单里演到了第四次,而这一次连「一道题一个答案」都不成立了。
| 300 轮 | 默认档 | ★ 全是单向边那一档 |
|---|---|---|
| 「双向当单向」被抓 | 80 | ★ 精确的 0 |
★ 方向同样可判:边少了 ⇒ 路线只会更少 ⇒ 答案恒 ≤ 正解(比正解大的是 0)。
★ 而那个 0 是结构性的:那一档里根本没有 z = 2,两版做的是同一件事。
6★ 对拍这一页
参照物照题面的定义硬算:传递闭包算出「谁能走到谁」,
再枚举买入城市 u、卖出城市 v,只要 1 → u → v → n 都通就是一对合法买卖。
300 轮(n 随机 5~9,照题面保证 1 能到 n、且无重边) |
默认档 | 换一档 |
|---|---|---|
| 分层图 ≡ 暴力 | ★ 不一致 0 轮 | —— |
| 正反两遍(反复松弛)≡ 暴力 | ★ 不一致 0 轮 | —— |
| 正反两遍但用 Dijkstra 的贪心 | 38 | DAG 档 ★ 0 |
| 拓扑序 DP | 270(★ 恒 ≤ 正解) | DAG 档 ★ 0 |
| 双向当单向 | 80(★ 恒 ≤ 正解) | 全单向档 ★ 0 |
| ⚠ 这一档答案本来就是 0 的轮数 | 3 | —— |
题面保证了两件事,随手写的生成器两件都不会自动成立:
- 「输入数据保证 1 号城市可以到达 n 号城市」⇒ 先按一个随机排列拉一条
1 → … → n的单向路径; - 「任意两个城市之间最多只有一条道路直接相连」⇒ 用一个
set挡掉重边。
★ 这是这一章里第三次干同一件事(P1339 保证连通、P1629 造单向环 保证强连通):「题面保证了什么」得由生成器亲手做到, 否则对拍比的是两个都没定义的东西。
7度量程序和生成器
8一页纸
| ★ 状态 | 「在哪儿 + 贸易做到哪一步」⇒ 三层图;「城市可以重复经过」在分层图上零代价 |
| ★★★ 让它落进 Dijkstra 射程的那一步 | 买入记 p、卖出记 100 − p ⇒ 总权 = 100 − 利润,所有边权非负;那个 100 来自题面最后一行「价格 ≤ 100」 |
| ★★★ 用 Dijkstra 的贪心做「正反两遍」 | 错的 —— 往前走只会让 minBuy 更小,「弹出即定死」的理由反过来了;最小反例 4 个点(minBuy[4] 该是 1,它给 7) |
| ⚠ 而 minBuy 错 ≠ 答案错 | DAG 档 300 轮它的答案一次没错(第 28 章 P3959 那条) |
| ⚠ 拓扑序 DP | 被抓 270 / 300,恒 ≤ 正解;⚠ 而题面那句「50% 的数据无环」正是给它留的分,DAG 档精确的 0 |
| ★★★ 双向边 | 「存一遍还是两遍」在这张题单里演到第四次,而这次同一张输入里 z 说了算 |