1 条题解
-
0
B. Iris 与树 详细题解
问题重述
给定一棵以 为根的树,顶点编号满足 DFS 序性质(每个子树内编号连续)。每条边 的权值 未知,但满足 且 。有 个事件,每个事件给定 。每次事件后,对于每个 (),需要最大化 ,然后输出这 个最大值的和。不同 的最大化可以独立选择未知边权。
关键观察
观察 1:DFS 序性质
由于顶点按 DFS 序编号,每个子树 的顶点编号构成连续区间:
其中 是子树 的大小。
观察 2:边 被哪些路径使用
对于边 ,考虑它连接的两个部分:
- 子树 (编号 到 )
- 树的其余部分
哪些 到 的路径会经过这条边?
- 如果 和 都在子树 内,路径不经过该边
- 如果 和 都在子树外,路径不经过该边
- 只有当 和 一个在子树内、一个在子树外时,路径才经过该边
由于编号连续,恰好有两个这样的 :
- (当 时): 在子树外, 在子树内
- (当 时): 在子树内, 在子树外
因此,每条边 最多被两条路径使用:
- 路径 :连接 (如果 )
- 路径 :连接 (如果 )
记 和 为经过边 的两条路径的编号(路径按起点编号标识)。
观察 3:路径长度的计算
对于路径 (连接 和 ),其长度等于经过的所有边的权值之和:
$$\operatorname{dist}(j, j+1) = \sum_{e \in \text{path}(j)} t_e $$观察 4:最大化策略
当某些边权未知时,要最大化某条路径的长度:
- 将所有权重分配给该路径上的未知边
- 其他路径不受影响(因为不同路径的最大化是独立的)
具体地,设路径 上已知边权之和为 ,未知边数量为 。那么最大可能长度为:
但这里需要注意:不同路径共享未知边时,不能同时分配全部剩余权重。然而题目说明:不同 的最大化可以独立选择未知边权。这意味着我们可以为每条路径分别考虑最优分配,即每条路径都可以假设自己独享所有剩余权重。
观察 5:更简单的计算方式
设总已知边权和为 。对于路径 :
- 如果路径上所有边的权值都已确定,则长度固定为
- 否则,可以给该路径上的某条未知边分配剩余权重 ,长度变为
但需要确保该未知边确实在路径上。实际上,每条路径的"自由度"取决于其上是否有未知边。
观察 6:标程的巧妙方法
标程维护了两个关键数组:
len[j]:路径 上未知边的数量c1[x]和c2[x]:经过边 的两条路径的编号
当一条边的权值被确定后:
- 该边从所有经过它的路径中"消失"(即该边不再是未知的)
- 如果某条路径的所有边都已知(
len[j] == 0),则该路径不再参与"灵活分配"
最终答案的计算公式:
$$\text{ans} = 2 \cdot sum + \text{sur} \cdot (w - sum) $$其中:
- :当前已确定的边权和
- :尚未被完全确定的路径数量(即至少还有一条未知边的路径数)
- 因子 是因为每条路径对应两个方向?实际上需要仔细分析
算法步骤
-
预处理:
- 读入 和父节点数组
- 计算每个节点的深度
dep[i] - 对于每条路径 (连接 和 ),找出它经过的所有边,并记录每条边被哪些路径使用
-
初始化:
len[j]= 路径 上的边数(初始时所有边未知)sur= (所有路径最初都未完全确定)
-
处理事件:
- 读入 ,表示
- 增加
- 对于经过边 的两条路径 和 :
len[c]减- 如果
len[c]变为 ,则sur减
- 输出
时间复杂度
- 预处理路径:(每条边最多被两条路径使用)
- 每个事件:
- 总复杂度:
完整代码
#include <bits/stdc++.h> using namespace std; const int MAXN = 200001; int fa[MAXN], dep[MAXN]; int c1[MAXN], c2[MAXN], len[MAXN]; int main() { ios_base::sync_with_stdio(false); cin.tie(0); int t; cin >> t; while (t--) { int n; long long w; cin >> n >> w; for (int i = 2; i <= n; i++) { cin >> fa[i]; } // 计算深度 dep[1] = 0; for (int i = 2; i <= n; i++) { dep[i] = dep[fa[i]] + 1; } // 初始化 for (int i = 1; i <= n; i++) { len[i] = 0; c1[i] = c2[i] = 0; } // 预处理每条路径 j (连接 j 和 j+1) 经过的边 for (int j = 1; j <= n; j++) { int x = j; int y = (j == n ? 1 : j + 1); while (x != y) { if (dep[x] < dep[y]) swap(x, y); // 边 x (连接 x 和 fa[x]) 被路径 j 使用 if (c1[x] == 0) c1[x] = j; else c2[x] = j; x = fa[x]; len[j]++; } } long long sum = 0; int sur = n; // 尚未完全确定的路径数量 for (int i = 1; i < n; i++) { int x; long long y; cin >> x >> y; sum += y; // 更新经过边 x 的两条路径 if (c1[x] && --len[c1[x]] == 0) sur--; if (c2[x] && --len[c2[x]] == 0) sur--; long long ans = 2 * sum + sur * (w - sum); cout << ans << " \n"[i == n - 1]; } } return 0; }代码说明
- 深度计算:
dep[i]表示节点 的深度,用于 LCA 计算 - 路径预处理:对于每条路径 ,向上爬到 LCA,记录经过的边
- 边与路径的映射:
c1[x]和c2[x]记录经过边 的两条路径 - 长度维护:
len[j]表示路径 上未知边的数量 - 答案计算:
- :已确定边对答案的贡献(每条边被两条路径使用?需要验证)
- :剩余权重分配给未完全确定的路径
示例验证
以第二个测试用例为例:
- ,树边:(星形树)
- 路径:
- :连接 ,经过边
- :连接 ,经过边 和
- :连接 ,经过边 和
- :连接 ,经过边
事件1:,
- 边 经过路径 和 ,
len[1]和len[2]各减 - 路径 的
len变为 (只有边 ),sur减 - 路径 的
len变为 (还剩边 ) sur = 3(路径 未完全确定)- 答案 = ✓
总结
本题的核心技巧:
- 利用 DFS 序性质,发现每条边最多被两条路径使用
- 将问题转化为维护每条路径上未知边的数量
- 答案公式
- 预处理和 事件处理
- 1
信息
- ID
- 6277
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 1
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者