有这么一类问题,要求统计一棵树上与子树相关的某些信息,比如:在一棵所有节点被染色的树上,统计每棵子树上出现次数最多的颜色编号之和。
很自然的可以想到用DFS序+主席树去求解,但是编码复杂度很高;
然后我们可以想到DFS序+莫队解决,然而$O(n\sqrt{n})$的时间复杂度在数据较大的时候容易TLE;
有没有更优美一点的解法呢?DSU On Tree(据说叫树上启发式合并)可以以较小的编码复杂度在$O(n\log n)$的时间复杂度完成对于所有子树信息的统计。
先上模板
1 void dsu(LL k,LL f,LL x){ 2 fore(i,k,v) 3 if(v!=f&&v!=son[k])dsu(v,k,0);//统计轻儿子所在的子树,不保留信息 4 if(son[k]) 5 dsu(son[k],k,1),hs=son[k];//统计重儿子所在子树,保留信息 6 calc(k,f,1);//统计所需信息 7 hs=0;ans[k]=sum; 8 if(!x)calc(k,f,-1),mx=sum=0;//如果是轻儿子,则清除信息 9 }
看上去除了每次保留了重儿子的信息和暴力也没啥区别。。。
但是实际上,只有dfs到轻边时,才会将轻边的子树中合并到上一级的重链,树剖后的每条链上至多有$log n$条轻边,所以每一个节点最多向上合并$log n$次,每次统计的复杂度是$O(n)$,整体复杂度为$O(nlog n)$
在竞赛中,DSU On Tree 是一个不错的trick,可以有效减少代码复杂度。然而其缺陷也是明显的,这种算法的适用范围非常狭窄,仅适用于对于子树信息的统计,并且不滋磁修改操作。
来看一道模板题:传送门