• 树链剖分 入门


    什么是树链剖分?

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

    如何树链剖分?

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

    重链剖分中有几个概念:

    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]) return;
        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)。

  • 相关阅读:
    树的统计Count---树链剖分
    HDU 1069---背包---Monkey and Banana
    《程序员代码面试指南》第三章 二叉树问题 判断t1树是否包含t2树全部的拓扑结构
    《程序员代码面试指南》第三章 二叉树问题 二叉树按层打印和ZigZag打印
    《程序员代码面试指南》第三章 二叉树问题 找到二叉树中最大搜索二叉树
    《程序员代码面试指南》第三章 二叉树问题 在二叉树中找见累加和为指定值的最长路径
    《程序员代码面试指南》第三章 二叉树问题 遍历二叉树的神级方法 morris
    《程序员代码面试指南》第三章 二叉树问题 二叉树的序列化和反序列化
    《程序员代码面试指南》第三章 二叉树问题 较为直观的打印二叉树
    《程序员代码面试指南》第三章 二叉树问题 逆时针打印二叉树的边界
  • 原文地址:https://www.cnblogs.com/Asika3912333/p/11417503.html
Copyright © 2020-2023  润新知