首页 > 编程语言 >算法总结-树链剖分

算法总结-树链剖分

时间:2024-08-23 20:15:08浏览次数:8  
标签:val 剖分 int top 树链 算法 void 序列 节点

算法总结-树链剖分

概念

  • 重儿子(红点):父节点 \(f\) 的子节点中子树大小最大的儿子

  • 轻儿子(蓝点):父节点 \(f\) 的子节点中除重儿子以外的节点

  • 重链(橙圈框出的链):链顶轻儿子,由重儿子组成的链。

    树链剖分-初识

重链剖分

用处:

  1. 将树上任意一条路径划分成不超过 \(\log{n}\) 条连续的链(如实现求 \(LCA\),优化时间复杂度等)。
  2. 划分出每条链上节点的 \(DFS\) 序,使树上的节点成为一个连续的序列,便于维护树上的操作(如线段树,分块等)。

实现:

  • 第一遍 \(DFS\):
  1. \(dep\) 数组:存储每个节点的深度

  2. \(father\) 数组:存储每个节点的父节点

  3. \(size\) 数组:存储每个节点的子树大小

  4. \(son\) 数组:存储每个节点的重儿子

    void dfs1(int x,int f){
        dep[x]=dep[f]+1;
        fa[x]=f;
        sz[x]=1;
        for(int i=0;i<e[x].size();i++){
            int y=e[x][i];
            if(y==f) continue;
            dfs1(y,x);
            sz[x]+=sz[y];
            if(sz[son[x]]<sz[y]) son[x]=y;//更新重儿子
        }
        return ;
    }
    
  • 第二遍 \(DFS\):

    1. \(top\) 数组:记录每条重链的链顶
    • 生成 \(dfs\) 序列(看情况是否需要):

      1. \(dfn\) 数组:记录每个点搜到的顺序,也就是 \(dfs\) 序。
      2. 记录生成序列的数组。
    void dfs2(int x,int t){
    	top[x]=t;
        dfn[x]=++did;
        val[did]=a[x];//生成dfs序列(因题而异)
        if(!son[x]) return ;//没重儿子说明也没有轻儿子
        dfs2(son[x],t);
        for(int i=0;i<e[x].size();i++){
    		int y=e[x][i];
            if(y==fa[x]||y==son[x]) continue;
            dfs2(y,y);
        }
        return ;
    }
    
  • 主函数:

    • 注:没有链的链顶为 \(0\) ,如果写成 \((1,0)\) ,则可能在某些情况下 \(TLE\)。
      int main(){
          ...
          dfs1(1,0);
          dfs2(1,1);//没有链的链顶为0,如果写成(1,0),则可能在有些情况下TLE
          ...
      }
    

运用

树链剖分求 \(LCA\)

  • 思路:将深度更深的点跳到链顶的父节点,当两点跳到同一条链上时,此时深度更浅的一点必定为原先两点的 \(LCA\) 。

  • 复杂度证明:因为已经被划分为 \(\log n\) 条链,所以最多只会有 \(log n\) 次跳到链顶的父节点,所以单次查询 \(LCA\) 的最坏时间复杂度为 \(O(\log n)\) 。

  • 求 \(LCA\) 部分代码:

    void LCA(int x,int y){
    	while(top[x]!=top[y]){//链顶不同,说明不在同一条重链上
    		if(dep[top[x]]<dep[top[y]]) swap(x,y);
            x=fa[top[x]];
        }
        return dep[x]<dep[y]?x:y;//比较谁是LCA
    }
    

维护树上操作

  • 思路:将树上的点按照 \(dfs\) 序生成一个连续的序列,使用这个新的序列来进行修改等操作。建树等部分和原来大,修改、查询等操作需要找到其对应的区间

    树链剖分-dfs序

  • 具体见题目要求。

    • 模板:P3384 【模板】重链剖分/树链剖分

    • 代码:

      • 生成新序列:

        void dfs2(int x,int t){
            top[x]=t;
            dfn[x]=++tid;
            val[tid]=a[x]%Mod;
            if(!son[x]) return ;
            dfs2(son[x],t);
            for(int i=0;i<e[x].size();i++){
                int y=e[x][i];
                if(y==fa[x]||y==son[x]) continue;
                dfs2(y,y);
            }
            return ;
        }
        
      • 建树。注:用新序列建树:

        void build(int k,int l,int r){
            if(l==r){
                sum[k]=val[l]%Mod;//注意这里用新序列建树
                return ;
            }
            int mid=(l+r)>>1;
            build(k<<1,l,mid);
            build(k<<1|1,mid+1,r);
            sum[k]=(sum[k<<1]+sum[k<<1|1])%Mod;
            return ;
        }
        
      • 新序列修改:

        void tree_modify(int x,int y,int v){
            while(top[x]!=top[y]){
                if(dep[top[x]]<dep[top[y]]) swap(x,y);//和求LCA一致
                modify(1,1,n,dfn[top[x]],dfn[x],v);//修改对应的新序列中的值
                x=fa[top[x]];
            }
            //当跳到同一条链上后还要进行一次,跳到同一节点。同一条链上的一段区间也要进行修改
            if(dep[x]<dep[y]) swap(x,y);
            modify(1,1,n,dfn[y],dfn[x],v);
            return ;
        }
        
      • 新序列查询:

        int tree_query(int x,int y){
            int ans=0;
            while(top[x]!=top[y]){
                if(dep[top[x]]<dep[top[y]]) swap(x,y);
                ans+=query(1,1,n,dfn[top[x]],dfn[x])%Mod;//同修改
                x=fa[top[x]];
            }
            //同修改
            if(dep[x]<dep[y]) swap(x,y);
            ans+=query(1,1,n,dfn[y],dfn[x])%Mod;
            return ans%Mod;
        }
        
      • 不懂的模拟一下样例:

        • 样例:

          /*#1
          5 5 2 24
          7 3 7 8 0 
          1 2
          1 5
          3 1
          4 1
          3 4 2
          3 2 2
          4 5
          1 5 1 3
          2 1 3
          */
          
        • 手摸:

          /*#1手玩 
           a :7 3 7 8  0
          dfn:2 1 5 3  4
          val:3 7 0 7  8
          //每行对应每次操作
          val:3 7 0 7 10
          val:5 9 2 9 12
          ans:2
          val:5 12 5 9 12
          ans:21
          */
          

To Be Continue

标签:val,剖分,int,top,树链,算法,void,序列,节点
From: https://www.cnblogs.com/Kx-Triumphs/p/18377015/tyccyt

相关文章

  • 机器学习—KNN算法-分类及模型选择与调优
    KNN算法-分类样本距离判断:欧氏距离、曼哈顿距离、明可夫斯基距离KNN算法原理:        K-近邻算法(K-NearestNeighbors,简称KNN),根据K个邻居样本的类别来判断当前样本的类别;如果一个样本在特征空间中的k个最相似(最邻近)样本中的大多数属于某个类别,......
  • 2024“钉耙编程”中国大学生算法设计超级联赛(10)
    时间:08_181011NOI2024 31.80%(703/2211)1008SunBian 55.02%(669/1216)1009不基本子串结构 20.57%(589/2864)1002scenery 21.00%(368/1752)1011NOI2024思路题目问的是“是否一定”,考虑最差情况,比自己排名高的全部拿分了,剩下的人一分不拿,与自己并列排名最后每场......
  • 代码随想录算法训练营第 50 天 |98. 所有可达路径
    代码随想录算法训练营Day50代码随想录算法训练营第50天|98.所有可达路径目录代码随想录算法训练营前言LeetCode98.所有可达路径一、图论基础概念1、图的种类2、度3、连通性:节点的连通情况4、图的构造5、图的遍历方式二、深度优先搜索1、深度优先搜索的三部曲2......
  • 代码随想录算法训练营第 51 天 |LeetCode99岛屿数量 LeetCode100.岛屿的最大面积
    代码随想录算法训练营Day51代码随想录算法训练营第51天|LeetCode99岛屿数量LeetCode100.岛屿的最大面积目录代码随想录算法训练营前言LeetCode200岛屿数量LCR105.岛屿的最大面积一、广度优先搜索基础1、用队列实现2、代码框架:二、卡码网99岛屿数量(LeetCode......
  • 代码随想录算法训练营第 49 天 |LeetCode42 接雨水 LeetCode84 柱状图中最大面积矩形
    代码随想录算法训练营Day49代码随想录算法训练营第49天|LeetCode42接雨水LeetCode84柱状图中最大面积矩形目录代码随想录算法训练营前言LeetCode42接雨水LeetCode84柱状图中最大面积矩形一、LeetCode42接雨水1.题目链接2.思路3.题解二、LeetCode84柱状......
  • 代码随想录算法训练营第 48 天 |LeetCode 739. 每日温度 LeetCode496.下一个更大元素
    代码随想录算法训练营Day48代码随想录算法训练营第48天|LeetCode739.每日温度LeetCode496.下一个更大元素ILeetCode503.下一个更大元素II目录代码随想录算法训练营前言LeetCode739.每日温度LeetCode496.下一个更大元素ILeetCode503.下一个更大元素II一......
  • 从龟速乘到 $Miller-Rabin$ 算法(数论算法总结)
    发现自己竟然菜到不太会龟速乘,所以把\(Miller-Rabin\)算法所需要用到的算法全学了一遍……龟速乘龟速乘是一种\(O(\logn)\)的乘法计算方法。考虑有时普通乘法取模会爆\(long\long\),因此我们考虑用类似快速幂的方式进行乘法运算。intmul(intx,inty,intc){ x%=c,y%=......
  • 每天学习一个基础算法之选择排序
    目录前言:一、选择排序的基本思路和实现方法1、基本思路2、实现方法二、选择排序的执行过程示意图三、选择排序的实现代码选择排序代码主体(以接口函数的形式)测试部分(主函数调用) 四、对选择排序复杂度的分析背景知识:1、选择排序的时间复杂度 精确计算:*采用大O......
  • 24暑假算法刷题 | Day39 | 动态规划 VII | LeetCode 198. 打家劫舍,213. 打家劫舍 II,33
    目录198.打家劫舍题目描述题解213.打家劫舍II题目描述题解337.打家劫舍III题目描述题解打家劫舍的一天......
  • 用Python实现9大回归算法详解——09. 决策树回归算法
    1.决策树回归的基本概念决策树回归(DecisionTreeRegression)是一种树状结构的回归模型,通过对数据集进行递归分割,将数据分成更小的子集,并在每个子集上进行简单的线性回归。决策树的核心思想是通过选择特征及其阈值来最大化每次分裂后的目标函数增益,从而找到使误差最小化的模型......