【学习笔记】树上启发式合并

【学习笔记】树上启发式合并

简介

树上启发式合并 dsu on tree,是一种 离线 算法,通过钦定一个更好的询问顺序,来优化时间复杂度,其实本质上还是暴力,有点类似莫队。有关 dsu on tree 的问题,常常是询问 子树内 的颜色数啥玩意的。

实现

对于每一个节点 \(u\),按以下顺序遍历:

  1. 遍历 \(u\)轻儿子 \(v\),计算它的答案,但并不保留它对答案数组的影响
  2. 遍历 \(u\)重儿子 \(v\),计算它的答案,保留它对答案数组的影响(注意 判断一个点是否有儿子,不然可能死循环)
  3. 将所有 轻儿子 的贡献计入答案数组

注意,第一,三步不能合并,因为需要独立计算每棵字数内的答案,但是又不能对每个结点都开一个数组(空间上会炸)

时间复杂度证明:
引理:一个叶子节点到根的路径上,最多有 \(log_n\)轻边
证明:每经过一条轻边,都会将树的的大小除以 \(2\)
对于一条重边,它的父亲节点不会遍历儿子节点(会直接继承),所以每个节点只会被遍历轻边个数 \(+1\)(还有它自己),因此最多时间复杂度 \(O(n\log n)\)

例题 颜色平衡树

题解

\(cnt[x]\) 表示颜色 \(x\) 的出现次数,\(ccnt[x]\),表示出现次数为 \(x\) 的颜色有多少个,则判断 \(u\) 的子树是不是颜色平衡树,只需要判断 \(ccnt[cnt[c[u]]] \times cnt[c[u]]\) 是否等于 \(size_u\),然后 dsu on tree。

code

#include<bits/stdc++.h>
#define pb push_back
#define fo(a, b, c) for(int b = a; b <= c; b++)
#define Fo(a, b) for(auto a : b)
#define N 200010
using namespace std;
int n, c[N], f, son[N], sz[N], ans;
int cnt[N], ccnt[N];
vector<int>G[N];
void dfs(int u){
	sz[u] = 1;
	Fo(v, G[u]){
		dfs(v, u);
		sz[u] += sz[v];
		if(sz[v] > sz[son[u]]) son[u] = v;
	}
}
void mdf(int u, int x){
	--ccnt[cnt[c[u]]], cnt[c[u]] += x, ++ccnt[cnt[c[u]]];
}
void add(int u, int x){
	mdf(u, x);
	Fo(v, G[u]) add(v, x);
}
void solve(int u, bool keep){
	Fo(v, G[u]) if(v ^ son[u]) solve(v, 0);
	if(son[u]) solve(son[u], 1);
	mdf(u, 1);
	Fo(v, G[u]) if(v ^ son[u]) add(v, 1);
	if(cnt[c[u]] * ccnt[cnt[c[u]]] == sz[u]) ++ans;
	if(!keep) add(u, - 1);
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin >> n;
	fo(1, i, n){
		cin >> c[i] >> f;
		if(f) G[f].pb(i);
	}
	dfs(1);
	solve(1, 1);
	cout << ans;
	return 0;
} 
posted @ 2025-08-16 09:51  GuoSN0410  阅读(20)  评论(0)    收藏  举报