zoukankan      html  css  js  c++  java
  • 树链剖分详解

    树链剖分是线段树的一个运用,也就是将一个树形结构的图转化到线段树中进行操作.

    先来看一下树链剖分能解决哪些问题:

    1. 树上最短路径的修改.
    2. 树上最短路径的区间求和.
    3. 树上子树的修改.
    4. 树上子树的求和.

    那么下面先介绍一些概念:

    1. 定义size(X)为以X为根的子树的节点个数
    2. 重儿子为一个节点的子节点中size值最大的节点
    3. 轻儿子为一个节点的非重儿子节点(一个节点有多个轻儿子)
    4. 重边是一个节点与重儿子的连边,轻边同理
    5. 重链是重边的连边,轻链同理

    然后是需要记录的一些变量:

    fa[]记录父亲,son[]记录重儿子,size[]记录节点的子节点个数,dep[]记录深度,top记录节点所在的当前链上的链顶,id[]记录在dfs序中的点权(树剖部分)

    sum[]记录区间和,lazy记录懒惰标记(线段树部分)

    last[]等数组记录链式前向星的建边,w[]记录点权

    下面是基本思路:

    1. 第一遍dfs从根节点记录好每个节点的fa[],size[],son[],dep[],找出重儿子
    2. 第二遍dfs将重儿子连成重链(即记录每个点的top[]),同时记录每个点在dfs序下的权值,及dfs序
    3. 将每个点的dfs序作为编号加入线段树的建树中
    4. 在进行修改,查询时直接采用(类似)线段树的操作

    于是先看dfs1吧

    也没啥很难的操作,大概就是一个找重儿子的过程,其他都简单.先上代码:

     1 void dfs1(int x,int deep,int f){
     2   dep[x]=deep;fa[x]=f;int maxson=-1;//每层递归中保留一个maxson和节点数比较,用于找重儿子
     3   for(int i=last[x];i;i=e[i].next){
     4     int to=e[i].to;
     5     if(to!=f){
     6       dfs1(to,deep+1,x);
     7       size[x]+=size[to];
     8       if(maxson<size[to]){//找重儿子的步骤
     9     maxson=size[to];
    10     son[x]=to;
    11       }
    12     }
    13   }
    14 }

    在递归中定义的maxson可以每层都保存一个值,找重儿子很方便.

    dfs2

    dfs2的操作是把每个重儿子连成一条条的重链,方便后面的操作(之后会讲).

    连重链事实上就是记录下每个点所在链的链顶,并且记录下第二遍dfs中每个节点进入搜索的时间戳(方便作为编号加入线段树).

    在连重链的时候先连重链,然后回溯上来再连轻链(因为每个非叶子节点必定有一个重儿子,所以这样可以遍历整张图).下面是代码: 

     1 void dfs2(la x,la tp){
     2   id[x]=++idx;tx[idx]=w[x];top[x]=tp;//tx[]记录在时间戳中第idx个点的权值,id[]记录每个点的dfs序
     3   if(!son[x]) return;
     4   dfs2(son[x],tp);//按重儿子搜到底,连完一条重链
     5   for(la i=last[x];i;i=e[i].next){
     6     la to=e[i].to;
     7     if(to==fa[x]||to==son[x]) continue;
     8     dfs2(to,to);//然后处理轻链,轻链的链顶就是自己
     9   }
    10 }

     线段树

    线段树的操作可以看一下之前一篇博客的讲解,然后在树剖中就是把每个节点按照它在dfs2中的顺序作为编号加入线段树中.

     1 void build(int root,int left,int right){
     2   if(left==right){
     3     sum[root]=tx[left];
     4     return;
     5   }
     6   build(ll(root),left,mid);
     7   build(rr(root),mid+1,right);
     8   sum[root]=sum[ll(root)]+sum[rr(root)];
     9   return;
    10 }

    这样加入线段树之后,就会有一些性质:

    同一条重链上的点是连成一段一段加入线段树中的,且链顶最先加入线段树,该链深度最深的节点id[x]=id[top[x]]+size[top[x]]-1;

    那么将信息加入了线段树中,要怎么对树进行操作呢?

    于是这里有了一个类似于lca倍增的操作,在树上跳链,从一条链到另一条链上.

    操作流程如下:

    1. 判断两个操作的点是否在同一条链上
    2. 如果不在同一条链上,则选一个深度更大的点向上跳,跳到链顶的父亲节点(这样必定会跳到另一条链上),并在沿途跳的路径进行要做的操作.
    3. 重复2,最终两个点会跳到同一条链上.
    4. 最后在同一条链上进行最后一次操作.
    void chainupdata(int a,int b,int val){
      while(top[a]!=top[b]){//流程1
        if(dep[top[a]]<dep[top[b]]) swap(a,b);//默认a为深度更深的点
        updata(1,1,n,id[top[a]],id[a],val);//在线段树中修改一个点到链顶
        a=fa[top[a]];//继续向上跳,直到两个点跳到同一条重链上
      }
      if(id[a]>id[b]) swap(a,b);//在同一条链上后,最后修改
      updata(1,1,n,id[a],id[b],val);
    }
    
    int chainquery(int a,int b){
      la res=0;
      while(top[a]!=top[b]){
        if(dep[top[a]]<dep[top[b]]) swap(a,b);
        (res+=query(1,1,n,id[top[a]],id[a]))%=mod;
        a=fa[top[a]];
      }
      if(id[a]>id[b]) swap(a,b);
      (res+=query(1,1,n,id[a],id[b]))%=mod;
      return res;//同理
    }

    在链上的操作只有这些.

    然后根据剖出树的性质,可以得出对子树进行操作的方法:

    1 int ans = query(1,1,n,id[x],id[x]+size[x]-1);

    修改同理.

    这样做的原因是因为在dfs2中打上时间戳的顺序,使得一个节点的子树中所有点的时间戳都在id[x],id[x]+size[x]-1的范围内.

  • 相关阅读:
    CREATE AGGREGATE
    技术文档列表
    jQuery 判断表单中多个 input text 中至少有一个不为空
    Java实现 蓝桥杯 算法提高 奥运会开幕式
    Java实现 蓝桥杯 算法提高 最长滑雪道
    Java实现 蓝桥杯 算法提高 最长滑雪道
    Java实现 蓝桥杯 算法提高 最长滑雪道
    Java实现 蓝桥杯 算法提高 最大值路径
    Java实现 蓝桥杯 算法提高 最大值路径
    Java实现 蓝桥杯 算法提高 最大值路径
  • 原文地址:https://www.cnblogs.com/BCOI/p/8151103.html
Copyright © 2011-2022 走看看