树上启发式合并
Posted Jozky86
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了树上启发式合并相关的知识,希望对你有一定的参考价值。
内容:
树上启发式合并(dsu on tree)对于某些树上离线问题可以速度大于等于大部分算法且更易于理解和实现的算法。
例题:
给出一棵 n个节点以 1为根的树,节点 u的颜色为cu ,现在对于每个结点 u询问 u子树里一共出现了多少种不同的颜色。
n<=2e5
树套树可以解决,如果可以离线的话,树上莫队复杂度带根号,现在我们要用一个带log的算法。
对于直接暴力复杂度为O(n^2),即对每一个子节点进行一次遍历,
对于每个节点的答案是由其子树和其本身得到的,现在考虑利用这个性质处理问题
我们预处理出每个节点子树的大小和其重儿子,重儿子同树链剖分一样,都是拥有节点最多子树的儿子,这个过程O(n)求
用cnt[i]表示颜色i出现的次数,ans[u]表示节点u的答案
对于一个节点u,我们按以下步骤遍历:
- 先遍历u的轻(非重)儿子,并计算答案,但不保留遍历后对cnt数组的影响
- 遍历重儿子,保留对cnt的影响
- 再遍历u的轻儿子的子树节点,加入这些结点的贡献,得到u的答案
对于一个节点,我们遍历一次重儿子,两次非重儿子,最划算
为什么不合并第一步和第三步呢,因为cnt数组不能重复使用,不然空间复杂度太高,需要O(n)内完成
若一个节点u被遍历了x次,则其重儿子被遍历x次,轻儿子被遍历2x次
int sz[N],son[N];
void dfs1(int x){
/* 求解重儿子 */
sz[x] = 1;
for(int i = head[x]; i; i = e[i].next){
int y = e[i].to;
dfs1(y); sz[x] += sz[y];
if(sz[y] > sz[son[x]]) son[x] = y;
}
}
void Delete(int x){
/* 删除的内容 */
for(int i = head[x]; i; i = e[i].next) Delete(e[i].to);
}
void modify(int x,int fa){
/* 更新的内容 */
for(int i = head[x]; i; i = e[i].next) modify(e[i].to,fa);
}
void ins(int x){
/* 插入的内容 */
for(int i = head[x]; i; i = e[i].next) ins(e[i].to);
}
void dfs2(int x){
/* 求解轻儿子并清空 */
for(int i = head[x]; i; i = e[i].next)
if(e[i].to != son[x]) dfs2(e[i].to), Delete(e[i].to);
/* 求解重儿子并保留 */
if(son[x]) dfs2(son[x]);
/* 用重儿子更新答案 */
/* 枚举轻儿子更新答案,并加入轻儿子 */
for(int i = head[x]; i; i = e[i].next)
if(e[i].to != son[x]) modify(e[i].to,x), ins(e[i].to);
/* 用所有儿子更新答案 */
}
应用
可以水一些树套树的题,也可以把树上莫队O(n√m)吊打
例题:
CF741D Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths
CF600E Lomsat gelral
UOJ284 快乐游戏鸡
CF570D Tree Requests
CF208E Blood Cousins
CF246E Blood Cousins Return
CF1009F Dominant Indices
CF375D Tree and Queries
以上是关于树上启发式合并的主要内容,如果未能解决你的问题,请参考以下文章
#线段树合并树上启发式合并#CF600E Lomsat gelral
CF 600 E Lomsat gelral —— 树上启发式合并