关于树链剖分
树链剖分
树链剖分可以解决树上路径,子树之类的一系列问题。
下面以[LuoguP3384]为例,讲解关于树链剖分的部分操作。
主要思想
树剖通过一种特殊的枚举方法,将树上的路径转化成连续的几段,通过线段树等操作去维护。
预处理
树链剖分的主要通过两个dfs求出以下需求的值
-
depth[x]:x的深度
-
fa[x]:x的父节点
-
sum[x]:包括节点x的子树一共有多少个节点
-
heavy_son[x]:x的重儿子
-
top[x]:x所在重链的首部
-
id[x]:x节点在线段树中对应的下标
下面对于一些量做出解释
重儿子:x的儿子中sum[x]最大的那个节点
重链:在树链剖分中,为了将一棵树剖分成连续的线段,通常采用从根节点开始,先访问重儿子,再访问其它儿子的方式,并将访问的时间顺序依次在线段树中将树上节点对应线段树的点1,2,3...n。
这样操作一条重链上的点所对应的线段树的节点就是连续的(即id[x]是连续的) 重链即表示在这条链上的节点编号是连续的。即top[x]到x的路径的点编号是连续的。(特殊的,1个点也算一条重链)
虽然量比较多,我也每次看到这里就不想学,但不用着急,等介绍了如何求后再去理解就不难了。
如何预处理
第一个dfs,求出depth,fa,sum,heavy_son
这应该是老套路了,简单求解。
void dfs1(int x,int father)
{
int maxson=0,SON=0;
depth[x]=depth[father]+1;fa[x]=father;sum[x]=1;
for(int k=lnk[x];k;k=las[k])
if(nex[k]!=father)
{
dfs1(nex[k],x);
sum[x]+=sum[nex[k]];
if(sum[nex[k]]>maxson)maxson=sum[nex[k]],SON=nex[k];
}
heavy_son[x]=SON;
}
第二个dfs,求出id,top
id数组按照枚举点的顺序就行了
关于求top,如果当前访问的是x的重儿子,top[x的重儿子]就和top[x]是一样的。(因为按照先重儿子的顺序top[x]到x的重儿子的编号是连续的,如果不是重儿子,那么top[x的重儿子]就是x的重儿子。(新的一段连续编号)
void dfs2(int x,int TOP)
{
id[x]=++cnt;newt[cnt]=t[x];top[x]=TOP;
if(heavy_son[x])dfs2(heavy_son[x],TOP);
for(int k=lnk[x];k;k=las[k])
if(nex[k]!=fa[x]&&nex[k]!=heavy_son[x])dfs2(nex[k],nex[k]);
}