前言
树链剖分:在一颗树上两点之间的路径的修改、求值。
原理
将一课树分成若干条链,将它们连起来,形成一条链,再用线段树等方法来维护、求值。
定义
在熟练剖分中,会使用到很多数组,这是它们的作用:
size[x]:在以x为根的子树中的节点的个数
son[x]:x的重儿子
deep[x]:x的深度
fa[x]:x的父亲
top[x]:x所在的链的顶部节点
aft[x]:x在合成的新链中的编号
bef[x]:x在树上的编号
那么重儿子就是某个节点的儿子中size[]值最大的节点,重边就是它们的之间的边,由重边连起来的链就是重链。那么其余的节点就是轻儿子,轻边就是它们的之间的边。
性质
一、轻边(u,v),size(v)<=size(u)/2。
二、从根到某一点的路径上,不超过O(logN)条轻边,不超过O(logN)条重路径。
实现
预处理
首先用一个dfs把size[]、son[]、deep[]、fa[]求出来。
int dg(int x)
{
size[x]=1;
int mx=0;
for(int i=last[x];i;i=next[i])
{
if(to[i]!=fa[x])
{
fa[to[i]]=x;
deep[to[i]]=deep[x]+1;
dg(to[i]);
size[x]+=size[to[i]];
if(size[to[i]]>mx)
{
mx=size[to[i]];
son[x]=to[i];
}
}
}
}
再用一个dfs求top[]、aft[]、bef[]以及重链:
从每一个轻儿子开始,沿着重边往下拉,直到拉到叶子节点为止。
最后,将所有的重链首尾相接,形成一条新的链,按照顺序,给每一个点一个新的编号。
int dg1(int x)
{
aft[x]=++tot;
d[tot]=x;
if(top[x]==0)
{
top[x]=x;
}
if(son[x])
{
top[son[x]]=top[x];
dg1(son[x]);
}
for(int i=last[x];i;i=next[i])
{
if(to[i]!=fa[x] && to[i]!=son[x])
{
dg1(to[i]);
}
}
}
修改、查询操作
当修改或查询u、v两个点的路径时
(1)当u、v在同一条重链上,即top[u]=top[v],显然直接修改aft[u]和aft[v]之间的值。
(2)当u、v不在同一条重链上,即top[u]<>top[v],如果deep[top[u]]>deep[top[v]],就修改aft[top[u]]和aft[u]之间的值,并将u赋值为fa[top[u]]。反之。
反复做(2)操作,直到变成(1)的情况。
时间复杂度O(NlogN)。
【ZJOI2008】树的统计
这道题可以用来练手,挺好滴。