dsu on tree,又名树上启发式合并、重链剖分,是一类十分实用的trick,它常常可以作为一些正解的替代算法:

1.DFS序+线段树/主席树/线段树合并

2.对DFS序分块的树上莫队

3.长链剖分(但复杂度会多一个log)

4.点分治(通常可以做有根树的点分治)

 

重链剖分的概念,用一个DFS找到每个点最大的一个儿子,作为它的重儿子,并将它标记。则从上到下一段连续的标记点就成为一条重链。

重链剖分有一个常用的性质:每个点到根的路径上,至多经过$O(\log n)$条重链。点分治、树链剖分都用到了这个性质。

dsu on tree 是一个优化后的暴力,主要优化的地方在于它先递归轻子树并消除影响,后递归重子树并保留影响。之后再计算该节点需要的信息。

它可以解决大部分无修改子树查询问题,需要问题满足以下几个条件:

1.有一个树上的$O(n^2)$暴力算法。

2.从轻子树合并上来的复杂度是线性的。

3.从重子树合并上来的复杂度是$O(1)$的。

4.可以在O(子树大小)时间内清空递归后的数组(也就是线性撤销所有影响)

 

dsu on tree的流程:

1.递归到所有轻儿子并消除影响。

2.递归到重儿子并保留影响。

3.递归所有轻儿子计算子树内除重子树之外的点对当前点答案的影响。

4.若此点不是父亲的重儿子则消除子树内所有影响(即将数组清空)。

 

另外,dsu on tree的题,只要使用的暴力数组是以深度为下标的,几乎都可以被长链剖分替代且复杂度少一个log。如下面的例2,3。

 

例一:[CF600E]询问每棵子树中出现次数最多的颜色(可能不只一个)的编号和。

首先考虑暴力算法,对每个点x DFS下去,得到一个计数器数组co[i]表示x的子树内颜色i的点的个数,同时维护x的答案。

然后重链剖分,按流程做即可,具体模板见代码。

 1 #include<cstdio>
 2 #include<algorithm>
 3 #include<iostream>
 4 #define rep(i,l,r) for (int i=(l); i<=(r); i++)
 5 #define For(i,x) for (int i=h[x],k; i; i=nxt[i])
 6 typedef long long ll;
 7 using namespace std;
 8 
 9 const int N=1000010;
10 int n,u,v,cnt,tot[N],mx,col[N],sz[N],son[N],h[N],to[N],nxt[N];
11 ll ans[N],sm;
12 bool skip[N];
13 
14 void add(int u,int v){ to[++cnt]=v; nxt[cnt]=h[u]; h[u]=cnt; }
15 
16 void get(int x,int fa){
17     sz[x]=1;
18     For(i,x) if ((k=to[i])!=fa){
19         get(k,x); sz[x]+=sz[k];
20         if (sz[k]>sz[son[x]]) son[x]=k;
21     }
22 }
23 
24 void dfs(int x,int fa,int op){
25     tot[col[x]]+=op;
26     if (op>0 && tot[col[x]]>=mx){
27         if (tot[col[x]]>mx) sm=0,mx=tot[col[x]];
28         sm+=col[x];
29     }
30     For(i,x) if ((k=to[i])!=fa && !skip[k]) dfs(k,x,op);
31 }
32 
33 void work(int x,int fa,bool cl){
34     For(i,x) if ((k=to[i])!=fa && k!=son[x]) work(k,x,1);
35     if (son[x]) work(son[x],x,0),skip[son[x]]=1;
36     dfs(x,fa,1); ans[x]=sm; skip[son[x]]=0;
37     if (cl) dfs(x,fa,-1),mx=sm=0;
38 }
39 
40 int main(){
41     scanf("%d",&n);
42     rep(i,1,n) scanf("%d",&col[i]);
43     rep(i,2,n) scanf("%d%d",&u,&v),add(u,v),add(v,u);
44     get(1,0); work(1,0,0);
45     rep(i,1,n) cout<<ans[i]<<' ';
46     return 0;
47 }
CF600E

相关文章: