0题目原文(本地存了一份)
原题在洛谷上(页头有链接)。别人的网站不归我们管,打不开、改版、题号调整都可能发生。 所以每个解析页都把题面转录一份存在本地,跟着仓库一起进版本库。
转录自洛谷 P1122,日期见页头。两边不一致时信原站。
题目描述
一株奇怪的花卉,上面共连有 n 朵花,共有 n−1 条枝干将花儿连在一起,
并且未修剪时每朵花都不是孤立的。每朵花都有一个「美丽指数」,该数越大说明这朵花越漂亮,
也有「美丽指数」为负数的,说明这朵花看着都让人恶心。
所谓「修剪」,意为:去掉其中的一条枝条,这样一株花就成了两株,扔掉其中一株。 经过一系列「修剪」之后,还剩下最后一株花(也可能是一朵)。
老师的任务就是:通过一系列「修剪」(也可以什么「修剪」都不进行), 使剩下的那株(那朵)花卉上所有花朵的「美丽指数」之和最大。
输入格式
第一行一个整数 n (1 ≤ n ≤ 16000),表示原始的那株花卉上共 n 朵花。
第二行有 n 个整数,第 i 个整数表示第 i 朵花的美丽指数。
接下来 n−1 行每行两个整数 a, b,表示存在一条连接第 a 朵花和第 b 朵花的枝条。
输出格式
一个数,表示一系列「修剪」之后所能得到的「美丽指数」之和的最大值。
保证绝对值不超过 2147483647。
说明/提示
数据范围及约定
- 对于
60%的数据,有1 ≤ n ≤ 1000; - 对于
100%的数据,有1 ≤ n ≤ 16000。
输入输出样例
输入
7 -1 -1 -1 1 1 1 0 1 4 2 5 3 6 4 7 5 7 6 7
输出
3
七朵花,7 号是那个把三条枝连起来的点。留下 4 5 6 7(1 + 1 + 1 + 0)⇒ 3。
⚠ 这一组样例只挡住了本页四个错法里的两个(忘了剪枝 1、只看根 2), 而「把所有正数加起来」和「答案初值写成 0」都原样输出 3 —— 见第 ① 步和第 ④ 步。
1★ 第一版:负的都剪掉,把正的加起来
这题的第一反应几乎人人相同:美丽指数是负的那些花,剪掉不就完了? 于是把所有正数一加,交上去。
// ✗ P1122 第一版:把所有「美丽指数为正」的花加起来//// 想法很顺:负的都剪掉不就完了吗?// ★ 它错在**连通性**:剪枝只能沿着枝条剪,剩下的必须还是**连在一起的一株**。// 两朵好花中间隔着一朵烂花,你没法只要两头。// ⇒ 它解的是一个**放宽了的问题**(不要求连通)⇒ **恒 ≥ 正解**。
#include <bits/stdc++.h>using namespace std;
int main() { ios::sync_with_stdio(false); cin.tie(nullptr);
int n; if (!(cin >> n) || n <= 0) return 0; vector<int> a(n + 1); for (int i = 1; i <= n; i++) cin >> a[i]; for (int i = 1; i < n; i++) { int x, y; cin >> x >> y; }
long long sum = 0; int mx = INT_MIN; for (int i = 1; i <= n; i++) { if (a[i] > 0) sum += a[i]; mx = max(mx, a[i]); } if (sum == 0) sum = mx; // 一朵都没有正的,那就留最好的那一朵 cout << sum << '\n'; return 0;}点「运行 ▶」看结果
剪刀只能剪枝条,剩下的必须还是连在一起的一株。 两朵好花中间隔着一朵烂花,你没有办法只要两头 —— 想要它们,就得把中间那朵一起留着。
⇒ 它解的是一个放宽了的问题(不要求连通)⇒ 恒 ≥ 正解。 这就是第 26 章 P1220 那条判据:答案偏大还是偏小,不用跑就能判。
| 它 ≥ 正解 | ★ 300 / 300 |
| 300 轮被抓 | 273 |
| 错的时候平均多报 | 51.72% |
| 最多多报 | 280.00% |
⚠ 而官方样例放过了它(那组数据的三朵正花恰好连成一片)。
2★★ 关键的一步:max(0, f[v]) —— 一个儿子亏了就剪掉
f[u] = 「必须留下 u」时,u 这一块能凑出的最大美丽指数和
= a[u] + Σ max(0, f[v])max(0, f[v]) 就是全部:某个儿子那一枝算出来是负的,剪掉它(题目允许),不亏;
是正的就接上。
★★ 答案是 max over 所有 u 的 f[u],不是 f[根] ——
留下来的那一株可以挂在树的任何地方,而 f[u] 只管「以 u 为最高点」的那些方案。
每个点都当一次最高点,就把所有方案数完了。
// P1122 最大子树和 —— 正解:树形 DP,O(n)//// f[u] = 「必须留下 u」时,u 这一块(在以 u 为根的子树里)能凑出的最大美丽指数和// = a[u] + Σ max(0, f[v])//// ★ `max(0, f[v])` 这一下就是全部:一个儿子那边算出来是负的,**剪掉它**(题目允许修剪),// 不亏;是正的就接上。// ★ 答案是 **max over u of f[u]** —— 不是 f[根]!留下来的那一株可以挂在树的任何地方。// ⚠ 而它也**不能取 max(0, ...)**:题面说修剪完「还剩下最后一株花(也可能是一朵)」// ⇒ 至少要留一朵 ⇒ 美丽指数全是负数时,答案是最大的那个负数,不是 0。
#include <bits/stdc++.h>using namespace std;
const int MAXN = 16005;int n, a[MAXN];vector<int> adj[MAXN];int f[MAXN];int ans;
void dfs(int u, int fa) { f[u] = a[u]; for (int v : adj[u]) { if (v == fa) continue; dfs(v, u); if (f[v] > 0) f[u] += f[v]; // 负的那一枝直接剪掉 } ans = max(ans, f[u]);}
int main() { ios::sync_with_stdio(false); cin.tie(nullptr);
if (!(cin >> n) || n <= 0) return 0; for (int i = 1; i <= n; i++) cin >> a[i]; for (int i = 1; i < n; i++) { int x, y; cin >> x >> y; adj[x].push_back(y); adj[y].push_back(x); }
ans = INT_MIN; // ← 不能写成 0 dfs(1, 0); cout << ans << '\n'; return 0;}点「运行 ▶」看结果
3★ 两个「收紧了的问题」:忘了剪枝、只看根
// ✗ P1122:忘了那个 max(0, ·),儿子那边算出来是负的也照单全收//// f[u] = a[u] + Σ f[v]//// ★ 它算了什么:它要求「留下 u 就必须把 u 的整棵子树全留下」——// **修剪这个动作被取消了**(只剩「从哪儿把整株砍下来」这一种选择)。// ⇒ 它解的是一个**收紧了的问题** ⇒ 恒 ≤ 正解。
#include <bits/stdc++.h>using namespace std;
const int MAXN = 16005;int n, a[MAXN], f[MAXN], ans;vector<int> adj[MAXN];
void dfs(int u, int fa) { f[u] = a[u]; for (int v : adj[u]) { if (v == fa) continue; dfs(v, u); f[u] += f[v]; // ← 少了 max(0, ·) } ans = max(ans, f[u]);}
int main() { ios::sync_with_stdio(false); cin.tie(nullptr); if (!(cin >> n) || n <= 0) return 0; for (int i = 1; i <= n; i++) cin >> a[i]; for (int i = 1; i < n; i++) { int x, y; cin >> x >> y; adj[x].push_back(y); adj[y].push_back(x); } ans = INT_MIN; dfs(1, 0); cout << ans << '\n'; return 0;}点「运行 ▶」看结果
// ✗ P1122:只输出 f[1],忘了对所有点取 max//// ★ 它算了什么:它要求**留下来的那一株必须含 1 号花**。// ⇒ 又一个收紧了的问题 ⇒ 恒 ≤ 正解。// ⚠ 而 1 号花是谁完全是随机的 —— 这就是「生成器不打乱编号,就会给某个点安排特殊身份」// (第 27 章正文第 ⑫ 步)在这道题上的样子。
#include <bits/stdc++.h>using namespace std;
const int MAXN = 16005;int n, a[MAXN], f[MAXN];vector<int> adj[MAXN];
void dfs(int u, int fa) { f[u] = a[u]; for (int v : adj[u]) { if (v == fa) continue; dfs(v, u); if (f[v] > 0) f[u] += f[v]; }}
int main() { ios::sync_with_stdio(false); cin.tie(nullptr); if (!(cin >> n) || n <= 0) return 0; for (int i = 1; i <= n; i++) cin >> a[i]; for (int i = 1; i < n; i++) { int x, y; cin >> x >> y; adj[x].push_back(y); adj[y].push_back(x); } dfs(1, 0); cout << f[1] << '\n'; // ← 只看了根 return 0;}点「运行 ▶」看结果
| 它其实解了哪道题 | 和正解的关系 | 300 轮被抓 | |
|---|---|---|---|
忘了 max(0, ·) |
「留下 u 就必须留下 u 的整棵子树」(修剪这个动作被取消了) | 恒 ≤(300/300) | 259 |
只输出 f[1] |
「留下来的那一株必须含 1 号花」 | 恒 ≤(300/300) | 151 |
两个都是收紧了的问题(多加了一条题目没有的限制)⇒ 只会少报。 ⇒ 加上第 ① 步那个「恒 ≥」,这一页三个错法方向齐了: 一个放宽、两个收紧 —— 和第 26 章 P1220 那页一样, 一页之内就能把这条判据的两个方向都验一遍。
⚠ 「只输出 f[1]」的抓获率还挂着一个前提:生成器有没有打乱编号。
不打乱的话 1 号点永远是那棵生成树的根,这个 bug 会被系统性地压低
(第 27 章正文第 ⑫ 步那条「别给某个点安排特殊身份」)。
4★★★ 第四个错法:答案初值写成 0 —— 而题面那句话是命门
// ✗ P1122:答案的初值写成了 0(以为「什么都不留」也算一种修剪)//// ★ 题面那句「经过一系列修剪之后,**还剩下最后一株花(也可能是一朵)**」是**命门**:// 至少要留一朵。所以美丽指数全是负数时,答案是**最大的那个负数**,不是 0。// ⇒ 被抓的轮数 ≡ 全部美丽指数都是负数的轮数 —— 而顺手写的生成器(正负都有)// 造出这种输入的概率是 2⁻ⁿ ⇒ 在默认档上它是**精确的 0**。
#include <bits/stdc++.h>using namespace std;
const int MAXN = 16005;int n, a[MAXN], f[MAXN], ans;vector<int> adj[MAXN];
void dfs(int u, int fa) { f[u] = a[u]; for (int v : adj[u]) { if (v == fa) continue; dfs(v, u); if (f[v] > 0) f[u] += f[v]; } ans = max(ans, f[u]);}
int main() { ios::sync_with_stdio(false); cin.tie(nullptr); if (!(cin >> n) || n <= 0) return 0; for (int i = 1; i <= n; i++) cin >> a[i]; for (int i = 1; i < n; i++) { int x, y; cin >> x >> y; adj[x].push_back(y); adj[y].push_back(x); } ans = 0; // ← 这里 dfs(1, 0); cout << ans << '\n'; return 0;}点「运行 ▶」看结果
题面那句「经过一系列修剪之后,还剩下最后一株花(也可能是一朵)」是命门: 至少要留一朵。所以美丽指数全是负数时,答案是最大的那个负数,不是 0。
| 档位(各 300 轮) | 「全是负数」的轮数 | 「初值写 0」被抓 |
|---|---|---|
默认档(−20 ~ 20) |
0 | ★ 精确的 0 |
全负档(−20 ~ −1) |
300 | ★ 300 |
⚠⚠ 上面那个「精确的 0」是配了自检才敢写的 —— 这正是本书那条通用规矩 (P2240、P1094):报「0 次 / 找不到」之前, 先拿一个已知能触发的档位证明这段代码是活的。 右边那一列 300 / 300 就是那份自检。
★ 而这个 0 是结构性的:默认档里 12 朵花全为负的概率约 2⁻¹²,加多少轮都指望不上。
⇒ 又一次「生成器最自然的默认值,正好是某个 bug 的藏身处」。
⚠ 顺带和同题单的 P1352 对照着看:那一页的全负档让两个 bug 一起隐身 (因为正解恒为 0),这一页的全负档是唯一抓得到这个 bug 的地方。 同一个档位,在两道题上作用正好相反。
5★★★ 要不要开 long long —— 这次不用跑就能证
题面只保证了答案的绝对值不超过 2147483647,可我们真正往数组里塞的是每个 f[u]。
它会不会先溢出?
两行就能证:f[u] 本身就是一个合法方案的美丽指数和(留下以 u 为最高点的那一块),
而答案是所有合法方案里最大的那个 ⇒ f[u] ≤ 答案,对每个 u 都成立。
300 轮里「每个 f[u] 都 ≤ 答案」 |
★ 300 / 300 |
⇒ 这和 P1164(「答案 ≤ 2³¹−1」正好是 32 位够用的充分条件)、 P5365(乘积不截断也不会溢出)是同一个形状的论证, 本书第三次用到它。⇒ 它是一个可复用的模式,不是巧合。
6★ 参照物、规模和递归深度
参照物是 2ⁿ 枚举「留下哪些花」,逐个检查它是不是连通的一株 ——
这道题真正难的地方就是连通性,而这一点只有小数据验得干净。
300 轮:正解 vs 2ⁿ 枚举连通块 |
★ 不一致 0 轮 |
顶格 n = 16000 排成一条链,递归到底 |
★ 16000 层,没爆栈 |
⚠ 最后这一行值得记住:16000 层递归默认栈放得下,
而同一张题单的 P3478 是 n ≤ 10⁶ —— 那儿就放不下了。
「递归会不会爆栈」是随 n 变的,每道题都要重新算一次。
7度量程序和生成器
8一页纸
| ★★ 关键的一步 | f[u] = a[u] + Σ max(0, f[v]);★ 答案是所有 f[u] 的 max,不是 f[根] |
| ★ 第一版 | 所有正数相加 ⇒ 放宽了连通性 ⇒ 恒 ≥ 正解,被抓 273/300,平均多报 51.72% |
| ★ 两个收紧 | 忘了 max(0,·)(≡「整棵子树都要」,被抓 259)/ 只看 f[1](≡「必须含 1 号」,被抓 151) |
| ★★★ 第四个错法 | 答案初值写成 0 ⇒ 被抓轮数 ≡ 全负轮数:默认档 0 ≡ 0、全负档 300 ≡ 300 |
| ⚠ 那个 0 配了自检 | 右边那 300 就是「这段代码是活的」的证明 |
| ★★★ 要不要 long long | f[u] 本身就是一个合法方案 ⇒ f[u] ≤ 答案 ⇒ 题面那句保证就够(300/300) |
| 参照物 | 2ⁿ 枚举连通块;300 轮不一致 0 轮 |
| 规模 | n ≤ 16000 的链递归 16000 层没爆栈;⚠ 到 P3478 的 10⁶ 就会 |