树链剖分 入门

什么是树链剖分?

树链剖分说白了就是将树的节点按照某种顺序编号,使其在特殊的链上编号连续(类似区间),方便用数据结构维护。

如何树链剖分?

树链剖分一般分为重链剖分和长链剖分,这里只介绍重链剖分(我也只会重链剖分)。

重链剖分中有几个概念:

  1. 重儿子:一个节点的所有子节点中,以某个子节点为根的子树中节点数量最多的称为重儿子(如果最多的数量相同随便取一个)。
  2. 轻儿子:一个节点的所有子节点中,不是重儿子的节点就是轻儿子(这不是废话吗)。
  3. 重边:父节点与重儿子所连成的边。
  4. 轻边:父节点与轻儿子所连成的边。
  5. 重链:重边所连成的链。
  6. 轻链:轻边所连城的链。

如图所示,蓝色的边为重边,绿色的节点为重儿子。

树链剖分的第一步就是一遍dfs找出所有节点的重儿子,并处理出深度与父子关系(以后有用)。

代码如下:

void dfs(int x){
    size[x]=1,d[x]=d[fa[x]];//size表示节点数量,d表示深度
    for(int i=head[x];i;i=nxt[i]){
        int y=ver[i];
        if(y==fa[x]) continue;//fa表父节点
     fa[y]=x; dfs(y); size[x]
+=size[y]; if(size[y]>size[son[x]]) son[x]=y;//son表示重儿子 } }

树链剖分的第二步便是按照重儿子先遍历,轻儿子后遍历的次序再dfs一遍,给节点按照dfs序编号,还是如上面那张图所示,红色数字的代表重新编号后的编号。并处理出每一条重链的首端,轻儿子的首端为它自己。

代码如下:

void dfs(int x,top1){//top1代表重链首端
    top[x]=top1;//首端
    id[x]=++cnt;//重新编号
    if(!son[x]) continue;
    dfs(son[x],top1);//重儿子所在重链的首端和该节点一样
    for(int i=head[x];i;i=nxt[i]){
        int y=ver[i];
        if(y==fa[x] || y==son[x]) continue;
        dfs(y,y);//轻儿子所在首端为它自己
    }
}

处理完这两个dfs之后,我们可以发现,重链上的编号都是连续的!,这意味着我们可以像维护区间一样用数据结构(线段树、树状数组)维护每一条重链。我们还可以发现每个节点的儿子的子树编号也是连续的,用线段树描述的话区间[id[x],id[x]+size[x]-1]覆盖了以节点x为根的子树中所有节点。

当我们想要在树链上维护树上两个节点x,y之间的路径的数据时(列如节点权值和)或者求lca时,如果x与y所在重链的首端不同的话,一般都是将节点x与节点y同时向其所在重链的首端的父节点往上跳,因为重链上节点编号是连续的,所以我们可以在跳的过程中对每一条重链用数据结构进行维护。当重链首端相同的时候也就意味着它们在同一条重链,这时候维护节点x~y(或y~x,看节点深度)之间的信息就好了。

下面以“将节点x到节点y的最短路径上的所有节点的权值都加上a”为例给出代码。

int add(int x,int y,int a){
    while(top[x]!=top[y]){//重链所在首端不同的时候
        if(d[top[x]]<d[top[y]]) swap(x,y);//比较深度
        addtree(1,id[top[x]],id[x],a);//线段树维护链,维护重链首端top[x]到节点x之间的节点权值
        x=fa[top[x]];//往上跳至首端的父节点
    }
  //此时首端相同
if(d[x]>d[y]) swap(x,y);//看深度 addtree(1,id[x],id[y],a);//维护此时x到y这条重链上的节点权值 }

两道例题:

    [SDOI2011]染色   题解
    [NO12015]软件包管理器   题解

对树链剖分的时间复杂度分析

树链剖分的两个性质:

  1. 若(x,y)是一条轻边,那么 $size[y]<\frac{1}{2} size[x]$
  2. 从根结点到任意结点的路所经过的轻重链的个数必定都小于logn

第一个性质不难理解,可以用反证法。第二个性质因为如果某一个节点x所在的是重链,那么它可以直接跳到重链链头,所以我们考虑最坏情况,也就是从它到根节点的路上,它都走的是轻边,再考虑对树上每一个节点都只有两个size数量相同的子节点(因为如果不相同那更快),此时就和一棵二叉树差不多,走到根节点所经过节点数差不多是 $log_2n$(我是这么理解的,出了错我可是不负泽任的233)。

转载于:https://www.cnblogs.com/Asika3912333/p/11417503.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值