题单 · 习题解析

洛谷 P2590 [ZJOI2008] 树的统计

★★★ 剖分那一半**一个字都不用改**,换的只有「线段树里放什么」(一个 `sum` → 一个 `sum` + 一个 `mx`,区间加 → 单点赋值)—— ★★★ 而这一「换」里唯一会咬人的是**幺元**:`sum` 的幺元是 0,`max` 的幺元是 **−∞**,而题面第一句数据范围就写着权值可以到 **−3×10⁴**;⇒ **换一种线段树,真正要重新想的不是「怎么合并」,是「什么都没有的时候该返回什么」**;★★★ 而这一页最该记的是那个 0:**顺手写的生成器(权值取 [0,100])和官方样例(权值 `4 2 1 3`)在这一格上一起瞎了** —— 不是「样例和对拍互补」,是两边同时放过主线错法,**只有把题面那半行读进去才救得回来**;★★★ 「触发 ≡ 抓获」这一页**两条六档一个不差**(幺元那条 ⟺ 存在一次 QMAX 路径最大值 < 0;「照抄边权版那个 +1」那条 ⟺ 漏掉浅端点会让答案变),而第三条(CHANGE 当成加)差 1.03~1.12 倍 —— **第三层没写出来**;★★ 「全程 `int` 一次都不会错」是能证的(`3×10⁴ × 3×10⁴ = 9×10⁸`,余量 **2.39 倍**),⚠ 而这个精确的 0 **配了自检**:那条线精确在**全树权值 71583**,而题面上限是 30000 —— 差 2.386 倍,抬过去当场分道扬镳;★★ 顶格那张表是这一章第二次撞见「顶格随机在骗人」(暴力比正解快 3.7 倍),⚠ 而它还多一层:**同样是一条链,只换「询问的两个端点怎么挑」就差 2.9 倍**(2.06 → 6.01 秒);⚠ 外加一条:「一整档在验零」也不保证所有错法都瞎掉 —— 权值全 0 那一档里 SkipLca 还活着 109 轮,**主语是「它错的方式」**

⚠ 先自己写一遍,再往下看

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

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

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

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

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

题目描述

一棵树上有 n 个节点,编号分别为 1 到 n,每个节点都有一个权值 w

我们将以下面的形式来要求你对这棵树完成一些操作:

  • I. CHANGE u t:把结点 u 的权值改为 t
  • II. QMAX u v:询问从点 u 到点 v 的路径上的节点的最大权值。
  • III. QSUM u v:询问从点 u 到点 v 的路径上的节点的权值

注意:从点 u 到点 v 的路径上的节点包括 uv 本身

输入格式

输入文件的第一行为一个整数 n,表示节点的个数。

接下来 n−1 行,每行 2 个整数 ab,表示节点 a 和节点 b 之间有一条边相连。

接下来一行 n 个整数,第 i 个整数 wᵢ 表示节点 i 的权值。

接下来 1 行,为一个整数 q,表示操作的总数。

接下来 q 行,每行一个操作,以 CHANGE u t 或者 QMAX u v 或者 QSUM u v 的形式给出。

输出格式

对于每个 QMAX 或者 QSUM 的操作,每行输出一个整数表示要求输出的结果。

数据范围

对于 100% 的数据,保证 1 ≤ n ≤ 3×10⁴0 ≤ q ≤ 2×10⁵

中途操作中保证每个节点的权值 w−3×10⁴ 到 3×10⁴ 之间。

时限 1 秒,内存 128000 KB(125 MiB)。

输入输出样例

输入

4
1 2
2 3
4 1
4 2 1 3
12
QMAX 3 4
QMAX 3 3
QMAX 3 2
QMAX 2 3
QSUM 3 4
QSUM 2 1
CHANGE 1 5
QMAX 3 4
CHANGE 3 6
QMAX 3 4
QMAX 2 4
QSUM 3 4

输出

4
1
2
2
10
6
5
6
5
16

★ 树是 1—22—34—1,初始权值 4 2 1 3。 ⚠ 注意读入顺序:n−1 条边,再 n 个权值 —— 和上一道 P3384 正好相反

1★★★ 剖分那一半一个字不用改 —— 换的是「线段树里放什么」

★★ 这道题和 P3384 的全部差别
P3384 这道题
线段树维护 一个 sum 一个 sum + 一个 mx
修改 区间加(要懒标记) 单点赋值(连懒标记都不用)
幺元 0 0 和 ★★ −∞
剖分那一半 两趟遍历、跳链、top / dfn / siz 一个字都不用改

⇒ ★★★ 而这一「换」里唯一会咬人的就是那个 −∞

题面写着「每个节点的权值 w−3×10⁴ 到 3×10⁴ 之间」—— 权值可以是负的。max 的幺元写成 0,等于凭空往每条路径上塞了一个权值为 0 的点

sum 那一半没有这个问题:加法的幺元本来就是 0。 ⇒ ★★ 换一种线段树,真正要重新想的不是「怎么合并」,是「什么都没有的时候该返回什么」。

★ 另外三条不在剖分里的

CHANGE u t 是赋值,不是加(P3384 四种操作里三种是「加」,这道题唯一的修改是「改为」);

输入顺序:先 n−1 条边,再 n 个权值 —— 和 P3384 反过来;

要不要 long long:一句乘法就问完了 —— 路径最多 n = 3×10⁴ 个点、|w| ≤ 3×10⁴|和| ≤ 9×10⁸, 而 int 上限 2 147 483 647int 就够,余量 2.39 倍(第 ⑥ 步有自检)。

2第一版:照题面爬

p2590Brute.cpp✗ 第一版(也是对拍的标准答案):两个点一起往上走,边走边收
p2590GenBig.cpp★ 顶格生成器:随机树 / 链 / 菊花 / 二叉树 / ★ 链且顶死两端
★★★ 顶格 n = 3×10⁴、q = 2×10⁵(时限 1 秒)—— 顶格随机上,暴力又比正解快
顶格形状 ✗ 暴力 ★ 正解
star(菊花) 0.02 秒 0.06 秒
rand(随机树) 0.03 秒 0.11 秒
binary(完全二叉树) 0.03 秒 0.15 秒
chain(链,端点随机) 2.06 秒 0.05 秒
chain + 每次都问两端 6.01 秒 0.02 秒

(本机 · A 机 WSL2 · nproc 12 · 2026-09-13 · 独占 · 每格 3 次取中位数。)

⇒ ★★ 这是这一章题单里第二次撞见同一件事:顶格随机那三档, 暴力比正解快 3.7 倍 —— 顺手造一组顶格随机数据,你会得出「这题不用学剖分」。

⇒ ★★★ 而这一页把「顶格 ≠ 最坏」又推深了一层: 同样是一条链、同样 q = 2×10⁵,只换「询问的两个端点怎么挑」就差 2.9 倍 (2.06 → 6.01 秒)。随机两个端点在链上平均只隔 n/3;顶死两端才是整整 n。 ⇒ ★ 顺带一条别把话说满的:链上随机端点是 2.06 秒 / 时限 1 秒 —— 它不是差得离谱,是差 2 倍

3正解:剖分照搬,线段树换一棵

p2590.cpp★ 正解:树链剖分 + 同时维护「和」与「最大值」的线段树
// ★ 正解:树链剖分 + 一棵**同时维护「和」与「最大值」**的线段树
//
// ============ 这道题和本章原题(P3384)差在哪儿 ============
//
// 剖分那一半 —— 两趟遍历、跳链、`top`/`dfn`/`siz` —— **一个字都不用改**。
// 换掉的只有线段树里放什么:
//
// P3384:一个 `sum`,支持「区间加」 幺元 0
// 这道题:一个 `sum` + 一个 `mx`,支持「单点赋值」 幺元 0 和 **−∞**
//
// ⚠⚠ 而这个「换」里唯一会咬人的就是那个 **−∞**:
// 题面写着「权值 `w` 在 **−3×10⁴ 到 3×10⁴** 之间」—— **可以是负的**。
// `mx` 的初值(以及跳链时那个累加器的初值)写成 0,负权路径上就会答出一个 0 来。
// ⇒ 这是本页第一个错法,也是这一页的主线。
//
// ★ 另外两条不在剖分里的:
// ① `CHANGE u t` 是**赋值**不是加;
// ② 输入顺序是「先 n−1 条边,再 n 个权值」—— 和 P3384 **正好相反**。
//
// ★ 要不要 `long long`:路径最多 `n = 3×10⁴` 个点,`|w| ≤ 3×10⁴`
// ⇒ `|和| ≤ 9×10⁸ < 2³¹−1 = 2.147×10⁹`,**`int` 就够,余量 2.39 倍**。
// 这里仍写 `long long`(代价是零),而 `p2590Int.cpp` 是那份 `int` 的 ——
// 它**一次都不会错**,见解析页第 ⑥ 步。
#include <bits/stdc++.h>
using namespace std;
static const int NEG = INT_MIN / 2;
static int n, q;
static vector<int> g[30005];
static int w[30005];
static int fa[30005], dep[30005], siz[30005], son[30005];
static int top_[30005], dfn[30005], rnk[30005], bfsOrder[30005], cnt;
/* ---------- 线段树:单点赋值 + 区间求和 / 区间最大值(没有懒标记) ---------- */
static long long sum_[120005];
static int mx_[120005];
static void pull(int p) {
sum_[p] = sum_[p << 1] + sum_[p << 1 | 1];
mx_[p] = max(mx_[p << 1], mx_[p << 1 | 1]);
}
static void build(int p, int l, int r) {
if (l == r) { sum_[p] = w[rnk[l]]; mx_[p] = w[rnk[l]]; return; }
int mid = (l + r) >> 1;
build(p << 1, l, mid); build(p << 1 | 1, mid + 1, r); pull(p);
}
static void setAt(int p, int l, int r, int pos, int v) {
if (l == r) { sum_[p] = v; mx_[p] = v; return; }
int mid = (l + r) >> 1;
if (pos <= mid) setAt(p << 1, l, mid, pos, v); else setAt(p << 1 | 1, mid + 1, r, pos, v);
pull(p);
}
static long long qSum(int p, int l, int r, int L, int R) {
if (L <= l && r <= R) return sum_[p];
int mid = (l + r) >> 1;
long long s = 0;
if (L <= mid) s += qSum(p << 1, l, mid, L, R);
if (R > mid) s += qSum(p << 1 | 1, mid + 1, r, L, R);
return s;
}
static int qMax(int p, int l, int r, int L, int R) {
if (L <= l && r <= R) return mx_[p];
int mid = (l + r) >> 1;
int s = NEG;
if (L <= mid) s = max(s, qMax(p << 1, l, mid, L, R));
if (R > mid) s = max(s, qMax(p << 1 | 1, mid + 1, r, L, R));
return s;
}
/* ---------- 两趟遍历(都不递归;顶格 n = 3×10⁴ 的链递归也危险,习惯别改) ---------- */
static void pass1() {
int head = 0, tail = 0;
bfsOrder[tail++] = 1; fa[1] = 0; dep[1] = 1;
while (head < tail) {
int u = bfsOrder[head++];
for (int v : g[u]) if (v != fa[u]) { fa[v] = u; dep[v] = dep[u] + 1; bfsOrder[tail++] = v; }
}
for (int i = 1; i <= n; i++) { siz[i] = 1; son[i] = 0; }
for (int i = n - 1; i >= 0; i--) {
int u = bfsOrder[i], p = fa[u];
if (!p) continue;
siz[p] += siz[u];
if (!son[p] || siz[u] > siz[son[p]]) son[p] = u;
}
}
static void pass2() {
static int st[30005];
int sp = 0; cnt = 0;
top_[1] = 1; st[sp++] = 1;
while (sp) {
int u = st[--sp];
dfn[u] = ++cnt; rnk[cnt] = u;
for (int v : g[u]) if (v != fa[u] && v != son[u]) { top_[v] = v; st[sp++] = v; }
if (son[u]) { top_[son[u]] = top_[u]; st[sp++] = son[u]; }
}
}
/* ---------- 路径:和「区间加」那道题一模一样的跳法 ---------- */
static long long pathSum(int x, int y) {
long long s = 0;
while (top_[x] != top_[y]) {
if (dep[top_[x]] < dep[top_[y]]) swap(x, y);
s += qSum(1, 1, n, dfn[top_[x]], dfn[x]);
x = fa[top_[x]];
}
if (dep[x] > dep[y]) swap(x, y);
return s + qSum(1, 1, n, dfn[x], dfn[y]); // ★ 端点也算:没有 +1
}
static int pathMax(int x, int y) {
int s = NEG; // ★★ 幺元是 −∞,不是 0
while (top_[x] != top_[y]) {
if (dep[top_[x]] < dep[top_[y]]) swap(x, y);
s = max(s, qMax(1, 1, n, dfn[top_[x]], dfn[x]));
x = fa[top_[x]];
}
if (dep[x] > dep[y]) swap(x, y);
return max(s, qMax(1, 1, n, dfn[x], dfn[y]));
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
if (!(cin >> n)) return 0;
for (int i = 1; i < n; i++) { int a, b; cin >> a >> b; g[a].push_back(b); g[b].push_back(a); }
for (int i = 1; i <= n; i++) cin >> w[i]; // ⚠ 权值在边之后,和 P3384 相反
pass1(); pass2();
build(1, 1, n);
cin >> q;
string op;
for (int i = 0; i < q; i++) {
int a, b; cin >> op >> a >> b;
if (op == "CHANGE") setAt(1, 1, n, dfn[a], b); // ★ 赋值,不是加
else if (op == "QMAX") cout << pathMax(a, b) << '\n';
else cout << pathSum(a, b) << '\n';
}
return 0;
}
点一下即可编辑
输入(stdin)
输出
点「运行 ▶」看结果
★ 值得多看一眼的只有三行
   static const int NEG = INT_MIN / 2;     <- ① 幺元。不是 0
   int s = NEG;                            <- ② 跳链时那个累加器的初值
   return max(s, qMax(1, 1, n, dfn[x], dfn[y]));
                            ^^^^^^         <- ③ 没有 +1:点权版要算 LCA 那一格

★ 第 ③ 行是本章第 12 步那个边权版的反面 —— 那儿写的是 dfn[x] + 1(跳过 LCA),因为边比点少一个。 ⇒ 而这道题问的是权,题面还专门写了一句「路径上的节点包括 u 和 v 本身」。 ⚠ 同一张题单里,P4315+1 不可、这道题 +1 就死。

4★ 三个错法 × 六个档位 —— 两条「触发 ≡ 抓获」逐格相同

p2590MaxZero.cpp✗ 错法①:QMAX 的累加器初值写成 0
p2590ChangeAdd.cpp✗ 错法②:把 CHANGE u t 当成了「加 t」
p2590SkipLca.cpp✗ 错法③:照抄边权版,收尾那一段左端 +1
p2590Int.cpp★ 对照:全程 int —— 看着像 bug,其实一次都不会错
p2590Zero.cpp★ 试金石:什么都不算,一律输出 0
p2590Gen.cpp★ 生成器:六个档位
p2590Count.cpp★ 数触发条件:有没有负的最大值 / CHANGE 改没改动东西 / 漏掉 LCA 会不会变
// P2590 的「触发条件计数器」:读一组数据,打一行 CSV
//
// negmax = 1 ⟺ 存在一次 QMAX,它那条路径上的最大值 **< 0**(①MaxZero 的触发条件)
// chgnz = 1 ⟺ 存在一次 `CHANGE u t` 且**当时** `w[u] ≠ 0`(②ChangeAdd 的第一层)
// chgseen = 1 ⟺ 上面那种 CHANGE 之后,还有一次询问真的碰到了那个 u(第二层)
// lcadiff = 1 ⟺ 存在一次询问,「漏掉浅的那个端点」会让答案变(③SkipLca 的触发条件)
// maxabs = 所有 QSUM 答案绝对值的最大值(④Int 的那条线:≥ 2³¹ 才可能烂)
//
// ★ 全部拿**暴力那条路**算,和被测的剖分代码一行都不共享。
#include <bits/stdc++.h>
using namespace std;
static int n, q;
static vector<int> g[30005];
static int w[30005], fa[30005], dep[30005], bfsOrder[30005];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
if (!(cin >> n)) return 0;
for (int i = 1; i < n; i++) { int a, b; cin >> a >> b; g[a].push_back(b); g[b].push_back(a); }
for (int i = 1; i <= n; i++) cin >> w[i];
int head = 0, tail = 0;
bfsOrder[tail++] = 1; fa[1] = 0; dep[1] = 1;
while (head < tail) {
int u = bfsOrder[head++];
for (int v : g[u]) if (v != fa[u]) { fa[v] = u; dep[v] = dep[u] + 1; bfsOrder[tail++] = v; }
}
cin >> q;
int negmax = 0, chgnz = 0, chgseen = 0, lcadiff = 0;
long long maxabs = 0;
vector<int> dirty(n + 1, 0);
string op;
for (int i = 0; i < q; i++) {
int a, b; cin >> op >> a >> b;
if (op == "CHANGE") { if (w[a] != 0) { chgnz = 1; dirty[a] = 1; } w[a] = b; continue; }
/* 走一遍路径,顺手把「路径上有哪些点」「谁是 LCA」都收下来 */
vector<int> pts;
int x = a, y = b;
while (dep[x] > dep[y]) { pts.push_back(x); x = fa[x]; }
while (dep[y] > dep[x]) { pts.push_back(y); y = fa[y]; }
while (x != y) { pts.push_back(x); pts.push_back(y); x = fa[x]; y = fa[y]; }
int lca = x;
long long s = 0; int mx = INT_MIN / 2;
for (int p : pts) { s += w[p]; mx = max(mx, w[p]); }
long long sNo = s; int mxNo = mx; // 不含 LCA 的那一版
s += w[lca]; mx = max(mx, w[lca]);
for (int p : pts) if (dirty[p]) chgseen = 1;
if (dirty[lca]) chgseen = 1;
if (op == "QMAX") { if (mx < 0) negmax = 1; if (mx != (pts.empty() ? INT_MIN / 2 : mxNo)) lcadiff = 1; }
else { maxabs = max(maxabs, s < 0 ? -s : s); if (s != sNo) lcadiff = 1; }
}
printf("negmax,%d\nchgnz,%d\nchgseen,%d\nlcadiff,%d\nmaxabs,%lld\n", negmax, chgnz, chgseen, lcadiff, maxabs);
return 0;
}
点一下即可编辑
输出
点「运行 ▶」看结果
★★★ 300 轮 × 六档(本机实测)
档位 ①MaxZero ②ChangeAdd ③SkipLca ④Int 试金石
0 ★ 顺手写法:权值一律非负 0 159 269 0 283
1 照题面(含负数) 95 147 271 0 283
2 权值全为负 245 146 276 0 283
3 一次 CHANGE 都不出 123 0 294 0 300
4 最终档 131 278 300 0 300
5 权值全为 0 0 0 109 0 0

★★★ ①MaxZero 那一档 0 就是这一页最该记的一格: 档 0 是所有人都会顺手写的那个生成器 —— 权值取 [0, 100]。 而题面第一句数据范围就写着权值可以到 −3×10⁴。 ⇒ 一个顺手的默认值,把这一页的主线错法整个测没了。「生成器该照抄题面的比值 / 范围,而不是你觉得顺眼的那个」的又一次。

⚠ 而档 5 那一行有意思:正确答案恒等于 0 ⇒ 试金石满分、三个错法全 0 —— 只有 ③SkipLca 还活着(109)。因为它错的方式不是「算小了」, 是u = vQMAX 上把区间跳成了空的,于是打出那个 −∞ 哨兵。 ⇒ ★★ 「一整档在验零」也不保证所有错法都瞎掉,主语是「它错的方式」

★★★ 两条「触发 ≡ 抓获」六档一个不差 —— 而第三条差 1.03 倍
第一层(触发条件) 抓获
①MaxZero 存在一次 QMAX,路径上的最大值 < 0:0 / 95 / 245 / 123 / 131 / 0 0 / 95 / 245 / 123 / 131 / 0 ★★★ 一个不差
③SkipLca 存在一次询问,「漏掉浅的那个端点」会让答案变:269 / 271 / 276 / 294 / 300 / 109 269 / 271 / 276 / 294 / 300 / 109 ★★★ 一个不差
②ChangeAdd 存在一次 CHANGE u t 且当时 w[u] ≠ 0而且之后有询问碰到 u:163 / 164 / 164 / 0 / 282 / 0 159 / 147 / 146 / 0 / 278 / 0 ⚠ 差 1.03~1.12 倍

★★ 前两条能写成 ,是因为它们的触发条件是一句能证的等价: ① max 的幺元多塞了一个 0 ⟺ 真实最大值 < 0 时答案变成 0; ③ 漏掉浅端点 ⟺ 那个点的权值影响到了答案。

⚠ 而 ②ChangeAdd 写不成 —— 第三层没写出来w[u] += tw[u] = t 巧合相等(w[u] 原来是 0), 或者那次 CHANGE 之后 u 又被另一次 CHANGE 重新写过。 ⇒ 「能不能写成 ≡,取决于你第一层写到第几格」,这一页三条各演了一次。

★ 官方样例挡住了两个,而放过的那一个原因写在题面第一行
样例上的输出
★ 正解 4 1 2 2 10 6 5 6 5 16
①MaxZero 4 1 2 2 10 6 5 6 5 16 放过 —— 样例权值 4 2 1 3 全是正的
②ChangeAdd 4 1 2 2 10 6 9 9 9 21 ✓ 挡住了
③SkipLca 3 −1073741824 1 1 6 2 3 6 3 11 ✓ 挡住了(而且第一个数就不对)

⇒ ★★ 而 ①MaxZero 被放过的原因和顺手写的生成器放过它的原因一模一样: 两边都只造了非负的权值。 ⇒ 这一次不是「样例和对拍互补」(第 50 章 P8306 那种), 是样例和对拍一起瞎只有把题面那半行数据范围读进去才救得回来。

5★ 「全程 int 一次都不会错」—— 而这个精确的 0 配了自检

p2590Line.cpp★ 探针(也是自检):一条 n = 30000 的链,所有权值都等于 w,只问一次 QSUM
★★ 报「一次都不会错」之前,先证明那段代码不是空壳

六档 1800 轮,int 那一版和正解逐字节全同。而它凭什么对,是一句乘法:

   路径最多 n = 3×10⁴ 个点,|w| ≤ 3×10⁴
   ⇒ |路径和| ≤ 9×10⁸ ,而 int 上限是 2 147 483 647
   ⇒ 余量 2.39 倍

⚠⚠ 而这个「精确的 0」配了自检 —— 把权值上界抬到题面之外,拿真程序两侧各跑一遍:

全树权值 w 30000 × w ★ 正解 int
71581 2 147 430 000 2147430000 2147430000
71582 2 147 460 000 2147460000 2147460000
71583 2 147 490 000 2147490000 −2147477296
71584 2 147 520 000 2147520000 −2147447296

⇒ ★★ 那条线精确在 w = 71583,而题面给的上限是 30000 —— 差 2.386 倍。 ⇒ 结论不是「可以写 int」,是「这道题的余量算得出来,而且只有 2.4 倍」; 正解仍然写 long long,因为代价是零。 (⚠ 对拍六档里最大的那个 |QSUM| 只有 142 682 —— 离 2³¹ 差 15 051 倍,对拍原理上够不着。)

6★ 哪一版就已经能过了

★★ 结论
版本 能过吗 数字(顶格最坏形状)
✗ 照题面爬 6.01 秒 / 时限 1 秒(⚠ 但顶格随机只要 0.03 秒)
max 的幺元写成 0 只要路径最大值是负的就错
CHANGE 当成加
✗ 照抄边权版那个 +1
★ 全程 int 一次都不会错(余量 2.39 倍)
正解 0.15 秒(最慢那一档)、峰值 7.5 MiB / 125 MiB

⇒ 这道题的三件功课: ① 幺元是 −∞,不是 0;② CHANGE 是赋值;③ 点权版不跳 LCA。

★ 一句话带走

「换一种线段树」听着像换零件,其实要重新想的只有一件事:什么都没有的时候该返回什么。

⇒ 求和的幺元是 0,所以 P3384 上这件事根本不用想; 求最大值的幺元是 −∞,而这道题的权值真的会是负的。 ⇒ ★★ 而顺手写的生成器、和出题人给的那组样例,在这一格上一起瞎了 —— 救得回来的只有题面那半行数据范围。