zoukankan      html  css  js  c++  java
  • LCA-树链剖分

    树链剖分

    在树上求LCA,我们常用的方法如:倍增法,离线Tarjin,转欧拉序,以及树链剖分。

    树剖与其他算法相比,时间复杂度相近,而优点是可以与线段树结合,支持修改

    原理:

    树剖就是将一棵树,根据子节点个数剖分成轻、重链,轻、重儿子,轻、重边。

    轻、重儿子:重儿子为该点子节点中,子树体积(size)最大的,其余的为轻儿子。对于每一节点,有且只有一个重儿子。

    轻、重边:重边是由重儿子与父亲节点组成的边,轻边是由轻儿子与父亲节点组成的边。

    轻、重链:重链是由连续的重边组成,其余的为轻边。

    同时,我们需要维护每条重链的链头,而对于一个轻儿子,其链头为本身。

    在开始求解LCA之前,我们需要两次搜索,来维护以下信息:

    重儿子son[i] 层数dep[i] 大小sz[i] 父亲节点fa[i] 链头top[i]

    并使用连接表完成对树的存储。

    第一次搜索,我们需要维护出重儿子、层数、大小、及父亲节点

     

    void dfs1(int x,int f)
    {
       sz[x]=1;
       dep[x]=dep[f]+1;
       for(int i=head[x];i;i=nxt[i])
      {
           int y=to[i];
           if(y==f) continue;
           dfs1(y,x);
           sz[x]+=sz[y];
           if(sz[y]>sz[son[x]]) son[x]=y;
      }
    }

    第二次搜索,我们需要维护链头

    void dfs2(int p,int tp)
    {
       top[p]=tp;
       if(son[p]) dfs2(son[p],tp);
       for(int i=head[p];i;i=nxt[i])
      {
           if(to[i]!=fa[p]&&to[i]!=son[p]) dfs2(to[i],to[i]);
      }
    }

    这里有一个细节,我们先搜索重儿子,其原因我们会在之后的线段树中做解释。

    最后,类似于倍增法,我们让两个查询点向上“跳”到其链头的父亲节点处

    这种做法的优点在于,如果该链长度巨大,我们可以直接沿着重边跳上去,节省时间。

    int lca(int x,int y)
    {
       while(top[x]!=top[y])
      {
           if(dep[top[x]]<dep[top[y]]) swap(x,y);
           x=fa[top[x]];      
      }
       if(dep[x]>dep[y]) swap(x,y);
       return x;
    }

    线段树:

    在我们第二次dfs中,我们可再维护出DFS序

    DFS序:在进行DFS时递归每个节点的顺序,每个节点仅进入一次。

    在此,区分一下欧拉序。

    欧拉序:DFS时的路线,及每个节点可以多次进出。

    例:

    在该树中:

    DFS序为:1 3 5 6 7 8 9 2 4

    欧拉序为: 1 2 4 2 1 3 5 6 5 7 5 3 8 3 1 9

    在上文中我们提到,要优先遍历重儿子,这样的好处是,在DFS序中,所有重链上的节点都是连续的

    于是,我们便可以用线段树对这些链进行修改。

    例:运用树剖进行一条路径上的区间查和

    在上文LCA函数中,我们每次会向上跳一条重链,此时,我们便可用DFS序和线段树进行区间的求和。

    int query_s(int l,int r,int x,int y,int p)
    {
       int mid=(l+r)>>1;
       if(x<=l&&y>=r) return s[p];
       int ret=0;
       if(x<=mid) ret+=query_s(l,mid,x,y,lson);
       if(y>mid) ret+=query_s(mid+1,r,x,y,rson);
       return ret;    
    }
    int qs(int x,int y)
    {
       int ret=0;
       while(top[x]!=top[y])
      {
           if(dep[top[x]]<dep[top[y]]) swap(x,y);
           ret+=query_s(1,n,idx[top[x]],idx[x],1);
           x=fa[top[x]];
      }
       if(dep[x]>dep[y]) swap(x,y);
       ret+=query_s(1,n,idx[x],idx[y],1);
       return ret;
    }

    例题:洛谷P2590 [ZJOI2008]树的统计

    题目传送门

    本题是一道裸的树剖,需要单点修改、区间求和、以及区间求最大值。

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    #define maxn 30010
    #define lson p<<1
    #define rson p<<1|1
    int head[maxn],to[maxn<<1],nxt[maxn<<1];
    int top[maxn],fa[maxn],son[maxn],dep[maxn],sz[maxn],idx[maxn];
    int n;
    int v[maxn];
    int num=0,tot=0,cnt=0;
    int s[maxn<<2],m[maxn<<2];
    #define inf -100000000
    #define MADE return
    #define BY 0
    #define JZW ;
    void ct(int l,int r,int p)
    {
      int mid=(l+r)>>1;
      printf("%d to %d m=%d s=%d ",l,r,m[p],s[p]);
      if(l==r)return;
      ct(l,mid,lson);
      ct(mid+1,r,rson);
    }
    int query_m(int l,int r,int x,int y,int p)
    {
      int mid=(l+r)>>1;
      if(x<=l&&y>=r)
      return m[p];
      int ret=inf;
      if(x<=mid) ret=query_m(l,mid,x,y,lson);
      if(y>mid) ret=max(ret,query_m(mid+1,r,x,y,rson));
      return ret;
    }
    int query_s(int l,int r,int x,int y,int p)
    {
      int mid=(l+r)>>1;
      if(x<=l&&y>=r) return s[p];
      int ret=0;
      if(x<=mid) ret+=query_s(l,mid,x,y,lson);
      if(y>mid) ret+=query_s(mid+1,r,x,y,rson);
      return ret;    
    }
    void build(int l,int r,int p)
    {
      int mid=(l+r)>>1;
      if(l==r)
      {
          s[p]=m[p]=v[l];
          return;
      }
      build(l,mid,lson);
      build(mid+1,r,rson);
    }
    void add(int x,int y)
    {
      to[++tot]=y;
      nxt[tot]=head[x];
      head[x]=tot;
    }
    void dfs1(int x,int f)
    {
      fa[x]=f;
      sz[x]=1;
      dep[x]=dep[f]+1;
      for(int i=head[x];i;i=nxt[i])
      {
          int y=to[i];
          if(y==f) continue;
          dfs1(y,x);
          sz[x]+=sz[y];
          if(sz[son[x]]<sz[y]) son[x]=y;
      }
    }
    void dfs2(int p,int tp)
    {
      top[p]=tp;
      idx[p]=++num;
      if(son[p]) dfs2(son[p],tp);
      for(int i=head[p];i;i=nxt[i])
      {
          int y=to[i];
          if(y!=fa[p]&&y!=son[p])
          dfs2(y,y);
      }
    }
    void update(int l,int r,int x,int v,int p)
    {
      int mid=(l+r)>>1;
      if(l==r)
      {
          m[p]=v;
          s[p]=v;
          return;
      }
      if(x<=mid) update(l,mid,x,v,lson);
      else update(mid+1,r,x,v,rson);
      m[p]=max(m[lson],m[rson]);
      s[p]=s[lson]+s[rson];
    }
    int qs(int x,int y)
    {
      int ret=0;
      while(top[x]!=top[y])
      {
          if(dep[top[x]]<dep[top[y]]) swap(x,y);
          ret+=query_s(1,n,idx[top[x]],idx[x],1);

          x=fa[top[x]];
      }
      if(dep[x]>dep[y]) swap(x,y);

      ret+=query_s(1,n,idx[x],idx[y],1);
      return ret;
    }
    int qm(int x,int y)
    {
      int ret=inf;
      while(top[x]!=top[y])
      {
          if(dep[top[x]]<dep[top[y]]) swap(x,y);
          ret=max(ret,query_m(1,n,idx[top[x]],idx[x],1));

          x=fa[top[x]];
      }
      if(dep[x]>dep[y]) swap(x,y);
      ret=max(ret,query_m(1,n,idx[x],idx[y],1));

      return ret;
    }
    int main()
    {
      scanf("%d",&n);
      for(int i=1;i<n;i++)
      {
          int x,y;
          scanf("%d%d",&x,&y);
          add(x,y);
          add(y,x);
      }
      dfs1(1,0);
      dfs2(1,1);
      for(int i=1;i<=n;i++)
      {
          int v;
          scanf("%d",&v);
          update(1,n,idx[i],v,1);
      }

      int q;
      scanf("%d",&q);
    int d=0;
      while(q--)
      {
          char op[10];
          scanf("%s",op);
          if(op[0]=='C')
          {
              int x,y;
              scanf("%d%d",&x,&y);
              update(1,n,idx[x],y,1);
          }
          if(op[1]=='M')
          {
              int x,y;
              scanf("%d%d",&x,&y);
              if(d==1) printf(" ");
              printf("%d",qm(x,y));
              d=1;
          }
          if(op[1]=='S')
          {
              int x,y;
              scanf("%d%d",&x,&y);
              if(d==1) printf(" ");
              printf("%d",qs(x,y));
              d=1;
          }
      }
      MADE BY JZW
    }

    注意:对于线段树的操作要输入对应的DFS序

  • 相关阅读:
    07_面向对象(成员变量和局部变量区别、类作为形式参数的问题、匿名对象、封装、private关键字、this关键字、构造方法、成员方法、static关键字、静态变量和成员变量、main方法)_02
    07_面向对象(成员变量和局部变量区别、类作为形式参数的问题、匿名对象、封装、private关键字、this关键字、构造方法、成员方法、static关键字、静态变量和成员变量、main方法)_01
    书单
    06_二维数组【四种格式、打印杨辉三角、Java中参数传递、数据加密案例】、面向对象【对象内存图】
    05_方法(重载)、数组(一维)[数组的初始化、遍历、求最值、逆序、查表]
    04循环结构
    基于视频压缩的实时监控系统
    select 和 epoll 的区别
    CGI 程序编写
    面经获取 20170515
  • 原文地址:https://www.cnblogs.com/Marcelo/p/13797478.html
Copyright © 2011-2022 走看看