1 条题解
-
0
E. 快乐的大学生活 题解
问题描述
给定一棵以 为根的树,共 个节点。每个节点 有一个颜色 ()。
$$f(u,v) = \operatorname{diff}(u,p) \cdot \operatorname{diff}(v,p) $$
定义 为从 到 的简单路径上不同颜色的个数。
对于一对节点 ,设 ,定义要求找出所有节点对 (允许 )中 的最大值。
思路分析
-
固定 LCA 考虑
对每个节点 ,考虑所有以 为最近公共祖先的节点对 。此时 和 分别位于 的不同子树中(或其中一个等于 )。
若我们能快速得到对于固定的 ,其每个子节点 的子树中 的最大值,那么取最大的两个值相乘即是以 为 LCA 的最优答案。 -
动态维护
我们采用 DFS 顺序处理:从根向下遍历,每当进入一个节点 时,我们希望知道从 到其子树内任意节点 的路径上有多少种不同颜色。
考虑在 DFS 过程中维护一个数组 ,表示当前根节点 到 的不同颜色数。当我们从 向下走到 时,颜色 会对 的整个子树产生贡献(+1),但如果子树中还存在另一个同色节点 ,那么从 到 的路径上该颜色只能计一次,因此需要撤销 的贡献。 -
最近同色祖先
定义 为节点 的祖先中颜色与 相同且离 最近的那个(若不存在则为 )。
那么当我们进入节点 时,颜色 的贡献应该只作用于 不包含任何 的子节点 的子树区域。具体地:- 给 的整个子树加 ;
- 对于每个满足 的节点 ,给 的子树减 。
通过这种方式,线段树中维护的 正好等于 ,其中 是当前正在处理的节点(DFS 栈顶)。
-
利用欧拉序转化为区间操作
对树做一次 DFS 求出每个节点的进入时间 和离开时间 ,则子树 对应区间 。
我们使用支持 区间加 和 区间最大值查询 的线段树。
算法步骤
步骤 1:计算 和 列表
- 初始化全局数组 表示当前 DFS 路径上颜色 最后出现的节点。
- 在 DFS 进入节点 时:
- ;
- 更新 ;
- 递归处理子节点;
- 回溯恢复 。
- 若 ,则将 加入 列表。
步骤 2:欧拉序预处理
- 第二次 DFS 得到每个节点的 和 。
步骤 3:第二次 DFS 计算答案
- 创建线段树,支持区间加和区间最大值。
- 执行
dfs2(u):- 区间 加 (加入颜色 的贡献)。
- 对 中的每个节点 ,区间 减 (撤销最近同色祖先的重复贡献)。
- 递归处理每个子节点 ,并在递归后查询区间 的最大值,存入列表
vals。 - 将节点 自身对应的值 也加入
vals(对应 的情况)。 - 对
vals降序排序,取前两个值 ,用 更新全局答案ans。 - 回溯:对 中的每个 ,区间加 ;对 的区间减 。
步骤 4:输出答案
复杂度分析
- 每个节点在
dfs1和dfs2中各被访问一次。 - 线段树每次区间修改和查询均为 。
- 总时间复杂度 ,空间复杂度 。
由于所有测试用例的 之和不超过 ,完全可行。
代码实现
#include <iostream> #include <vector> #include <algorithm> #include <cstring> using namespace std; const int MAXN = 300005; int n; vector<int> children[MAXN]; int a[MAXN]; int up[MAXN]; // nearest ancestor with same color int last[MAXN]; // last occurrence of each color in current path vector<int> same[MAXN]; // vertices for which this vertex is the nearest same-color ancestor int in[MAXN], out[MAXN], timer; int euler[MAXN]; // not really needed // Segment tree for range add and range max struct SegTree { int n; vector<int> maxv, lazy; SegTree(int _n) : n(_n), maxv(4 * _n), lazy(4 * _n) {} void push(int idx) { if (lazy[idx]) { maxv[idx*2] += lazy[idx]; maxv[idx*2+1] += lazy[idx]; lazy[idx*2] += lazy[idx]; lazy[idx*2+1] += lazy[idx]; lazy[idx] = 0; } } void update(int l, int r, int val, int idx, int L, int R) { if (l <= L && R <= r) { maxv[idx] += val; lazy[idx] += val; return; } push(idx); int mid = (L + R) / 2; if (l <= mid) update(l, r, val, idx*2, L, mid); if (r > mid) update(l, r, val, idx*2+1, mid+1, R); maxv[idx] = max(maxv[idx*2], maxv[idx*2+1]); } int query(int l, int r, int idx, int L, int R) { if (l <= L && R <= r) return maxv[idx]; push(idx); int mid = (L + R) / 2; int res = 0; if (l <= mid) res = max(res, query(l, r, idx*2, L, mid)); if (r > mid) res = max(res, query(l, r, idx*2+1, mid+1, R)); return res; } void update(int l, int r, int val) { update(l, r, val, 1, 1, n); } int query(int l, int r) { return query(l, r, 1, 1, n); } }; void dfs1(int u) { up[u] = last[a[u]]; last[a[u]] = u; for (int v : children[u]) { dfs1(v); } last[a[u]] = up[u]; if (up[u] != 0) { same[up[u]].push_back(u); } } void dfs_euler(int u) { in[u] = ++timer; for (int v : children[u]) { dfs_euler(v); } out[u] = timer; } long long ans; SegTree *seg; void dfs2(int u) { // add current vertex's color seg->update(in[u], out[u], 1); // subtract for vertices that have u as nearest same-color ancestor for (int v : same[u]) { seg->update(in[v], out[v], -1); } vector<int> vals; // process children for (int v : children[u]) { dfs2(v); int cur = seg->query(in[v], out[v]); vals.push_back(cur); } // add u itself (value = 1) vals.push_back(1); // find top two sort(vals.rbegin(), vals.rend()); if (vals.size() >= 2) { ans = max(ans, 1LL * vals[0] * vals[1]); } else if (vals.size() == 1) { ans = max(ans, 1LL * vals[0]); } // rollback for (int v : same[u]) { seg->update(in[v], out[v], 1); } seg->update(in[u], out[u], -1); } void solve() { cin >> n; for (int i = 1; i <= n; i++) { children[i].clear(); same[i].clear(); } for (int i = 2; i <= n; i++) { int p; cin >> p; children[p].push_back(i); } for (int i = 1; i <= n; i++) { cin >> a[i]; } // initialize last array with 0 memset(last, 0, sizeof(last)); dfs1(1); timer = 0; dfs_euler(1); seg = new SegTree(n); ans = 1; dfs2(1); cout << ans << "\n"; delete seg; } int main() { ios::sync_with_stdio(false); cin.tie(0); int t; cin >> t; while (t--) { solve(); } return 0; }总结
本题的核心思想是 在 DFS 过程中动态维护从当前节点到子树内任意节点的不同颜色数,并利用 最近同色祖先 来避免重复计数。通过欧拉序将子树转化为区间,使用线段树支持区间加和区间最大值,最终在 时间内求出答案。该解法巧妙地将树上的路径计数问题转化为区间操作,值得深入理解。
-
- 1
信息
- ID
- 6496
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 9
- 标签
- 递交数
- 2
- 已通过
- 1
- 上传者