树链剖分的思想是通过轻重链剖分将树分为多条链,保证每个节点都属于且只属于一条链。树链剖分是轻重链剖分,节点到重儿子(子树节点数最多的儿子)之间的路径为重链。每条重链都相当于一段区间,把所有重链收尾相接组成一个线性节点序列,再通过数据结构(如树状数组,SBT,伸展树,线段树等)来维护即可。
若表示以为根的子树的节点个数,则在的所有儿子中,最大的儿子就是重儿子,而的其他儿子都是轻儿子,当前节点与其重儿子之间的边就是重边,多条重边相连为一条重链。
重要性质
(1)若是轻儿子,是的父节点,则
(2)从根到某一点路径上,不超过条重链,不超过条轻边。
树链剖分支持以下操作
(1)单点修改:修改一个点的权值
(2)区间修改:修改节点u到v路径上节点的权值
(3)区间最值查询:查询节点u到v路径上节点的最值
(4)区间和查询:查询节点u到v路径上节点的和值。
预处理
树链剖分可以采用两次深度优先搜索实现
第1次深度优先搜索维护4个信息:
(1):x的深度
(2):x的父节点
(3):以x为根的子树的节点数
(4):x的重儿子,为重边
第2次深度优先搜索以优先走重边的原则,维护3个信息:
(1):x所在的重链上的顶端节编号(重链上深度最小的节点)
(2):x在节点序列中的位置下标。
(3):树链剖分后节点序列中第x个位置的节点。
和互逆。
预处理时间复杂度为
求解LCA问题
对于LCA问题,点和边均没有权值,因此无需维护线段树来实现。先进行树链剖分预处理。
void dfs1(int x)
{
size[x]=1;
for(int i=head[x];i;i=nxt[i])
{
int y=to[i];
if(y==fa[x]) continue;
dep[y]=dep[x]+1;
fa[y]=x;
dfs1(y,x);
size[x]+=size[y];
if(size[y]>size[son[x]]) son[x]=y;
}
}
void dfs2(int x,int t)
{
id[x]=++cnt;
rev[cnt]=x;
top[x]=t;
if(!son[x]) return;
dfs2(son[x],t);//优先重儿子
for(int i=head[x];i;i=nxt[i])
{
int y=to[i];
if(y==fa[x] || y==son[x]) continue;
dfs2(y,y);//新的重链
}
}
树上的任意一对节点只存在两种情况:①在同一重链上()②不在同一条重链上
对于第①种情况,LCA(x,y)就是x,y中深度较小的节点。
对于第②种情况,只要想办法将x,y两点转移到同一条重链上即可。首先求出x,y所在重链的顶端节点,将其顶端节点深度大的节点上移(转移到另一条重链),知道x,y在同一条重链上,再用第①种情况中的方法求解即可。
int LCA(int x,int y)
{
while(top[x]!=top[y])
if(dep[top[x]]>dep[top[y]])
x=fa[top[x]];
else
y=fa[top[y]];
return dep[x]<dep[y]?x:y;
}
树链剖分与线段树
若在树上进行点更新、区间更新、区间查询等操作,可以使用线段树来维护和处理。
轻重链的剖分方式决定了一个节点的子树节点在节点序列中是连续的。
查询节点x到y路径上节点权值的最值与和值的方法如下
(1)若u,v在同一重链,则在线段树上查询其对应的下标区间即可。
(2)若u,v不再同一条重链上,则一边查询,一边将u,v向同一条重链上移,然后采用上面的方法处理。对于顶端节点深度大的节点,先查询其到顶端节点的区间,然后一边上移一边查询,直到上移到同一条重链上,再查询在同一条重链上的区间。