树上启发式合并(dsu on tree)学习笔记
严格上来说,dsu on tree 是一种遍历方式。
有时,我们不得不完整遍历每棵子树来找出每个子树的信息,或者说没有比遍历子树更优的方案,就可以尝试使用 dsu on tree 来遍历,可以直接 \(n ^ 2 \to n \log n\)。
例题 CF600E,让你求每个子树的所有众数之和。
初始做法:暴力遍历每棵子树。\(n ^ 2\)。
进阶做法:先处理 dfs 序,转化为序列上的区间查询,莫队求解。
树上启发式合并
考虑普通的 DP 流程,即对每个点开一个桶,暴力转移。这样时空都是 \(n ^ 2\) 双双爆炸,这还不如直接暴力遍历每棵子树。
那么我们考虑开一个全局的桶 \(buc\),记录每个颜色出现了多少次,维护当前桶中的众数之和是多少。
那么考虑以下流程,调用 \(solve(root)\),定义 \(solve(u)\) 函数如下(每个节点只取一个重儿子):
-
对于 \(u\) 的所有非重儿子 \(v\):清空 \(buc\),\(solve(v)\)。
-
清空 \(buc\)。
-
如果 \(u\) 不是叶子,对于 \(u\) 的重儿子 \(v_0\),\(solve(v_0)\)。
-
然后通过遍历 \(v_0\) 之外所有 \(u\) 的子树,将这些点加入 \(buc\) 中。
-
将 \(u\) 的信息加入 \(buc\),那么此时 \(buc\) 中的和即为 \(u\) 的答案。
不难发现,这样相对于朴素的遍历,每个节点的重儿子少遍历的一次,我们来证明它的时间复杂度是 \(\mathcal{O(n \log n)}\) 的。
时间复杂度证明
考虑贡献法,节点 \(u\) 被遍历的次数应该是 \(u 到 root 的轻边数量 + 1\)。
然后一个点到 \(root\) 的轻边数量不超过 \(\log n\),所以总复杂度是 \(\mathcal{O(n \log n)}\) 的。
CF600E
// Code by GENX.
// 2026-06-04
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 7;
int n, a[N], sz[N], ans[N], son[N];
vector<int> g[N];
struct buc{
int cnt[N], res, mx;
queue<int> q;
void init(){
while(q.size()) cnt[q.front()] --, q.pop();
mx = res = 0;
}
void add(int x){
cnt[x] ++, q.push(x);
if(cnt[x] > mx) mx = cnt[x], res = x;
else if(cnt[x] == mx) res += x;
}
} Misaka;
void cal(int u, int pre){
sz[u] = 1;
for(int v: g[u]){
if(v != pre){
cal(v, u);
sz[u] += sz[v];
if(sz[v] > sz[son[u]]) son[u] = v;
}
}
}
void ext(int u, int pre){
Misaka.add(a[u]);
for(int v: g[u]){
if(v != pre) ext(v, u);
}
}
void dfs(int u, int pre){
for(int v: g[u]){
if(v != pre && v != son[u]){
Misaka.init(), dfs(v, u);
}
}
Misaka.init();
if(son[u]) dfs(son[u], u);
for(int v: g[u]){
if(v != pre && v != son[u]) ext(v, u);
}
Misaka.add(a[u]);
ans[u] = Misaka.res;
}
signed main(){
ios::sync_with_stdio(0), cin.tie(0);
cin >> n;
for(int i = 1; i <= n; i ++) cin >> a[i];
for(int i = 1; i < n; i ++){
int u, v; cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
cal(1, 0);
dfs(1, 0);
for(int i = 1; i <= n; i ++) cout << ans[i] << " ";
return 0;
}

浙公网安备 33010602011771号