zoukankan      html  css  js  c++  java
  • 树链剖分总结

    什么是树链剖分?

      对于一(heng)些(duo)在树上对路径维护的题,就算暴力倍增找lca也会TLE。为此,我们以某种规则将一棵树剖分成若干条竖直方向上的链,每次维护时可以一次跳一条链、并借助一些强大的线性数据结构来维护(通常链的数量很少),这样就大大优化了时间复杂度,足以解决很多线性结构搬到树上的题目。

    前置知识:

      以线段树为代表的各种强大的数据结构;dfs序(化树形为线性)

    学习前先了解一些新概念:

      重儿子:父亲节点的所有儿子中子树结点数目最多(size最大)的结点;

      轻儿子:父亲节点中除了重儿子以外的儿子;

      重边:父亲结点和重儿子连成的边;

      轻边:父亲节点和轻儿子连成的边;

      重链:由多条重边连接而成的路径;

      轻链:由多条轻边连接而成的路径;

    通常我们剖分的规则是重链剖分法,即先把重儿子分到一个链上,再去管其余轻儿子。这样做在大多数情况下得到的长链较多,而且有经证明的时间复杂度上限。

    这样肯定要先来一遍dfs找到所有重儿子,并预处理一些点的基本信息:深度、父亲节点、字树大小;之后再来一遍dfs,剖分树链(记录每个点的链头),并记录此时的dfs序,将树的节点需维护的信息按dfs序存到一个线性结构中维护。两遍dfs后就在线性结构的上面建一棵线段树(一般都是线段树(毕竟太强大了、太容易被考了),有时换成树状数组也行,看题目而定啦)。

    见代码:

     1 void dfs1(int u,int fa,int deep)//第一遍dfs 
     2 {
     3     dep[u]=deep;//深度 
     4     siz[u]=1;//子树大小一开始为1(它自己) ,之后再加它儿子的子树大小就得到的它真正的子树大小了 
     5     int maxs=0,maxd=0;
     6     int t;
     7     for(int e=lst[u];e;e=nxt[e])
     8     {
     9         t=to[e];
    10         if(t!=fa)
    11         {
    12             father[t]=u;
    13             dfs1(t,u,deep+1);
    14             siz[u]+=siz[t];
    15             if(siz[t]>maxs)//找重儿子 
    16             {
    17                 maxs=siz[t];
    18                 maxd=t;
    19             }
    20         }
    21     }
    22     son[u]=maxd;//重儿子 
    23 }
    24 
    25 int dfsxu;
    26 
    27 void dfs2(int u,int fa,int ding)//第二遍dfs 
    28 {
    29     top[u]=ding;//链头 
    30     dfs[u]=++dfsxu;//记录dfs序 
    31     xu[dfsxu]=num[u];//按dfs序记录的点要维护的信息 
    32     if(!son[u])
    33         return;
    34     dfs2(son[u],u,ding);//先把重儿子纳入父亲的链中 
    35     int t;
    36     for(int e=lst[u];e;e=nxt[e])
    37     {
    38         t=to[e];
    39         if(t!=fa&&t!=son[u])//其他轻儿子自辟一链 
    40             dfs2(t,u,t);
    41     }
    42 }
    dfs核心代码

    我们看看dfs序,发现同一条树链上的节点dfs序都是连续的,同时以一个点为根的子树内所有点的dfs序也是连续的,这样导致什么结果?你若想维护一棵子树或一条树链上的信息,只需知道最小和最大的dfs序,便可用数据结构维护这个区间,缩小维护的时间复杂度(比暴力什么的好太多啦)。

    之后维护时怎么办?这里单讲维护x到y路径的情况(维护以u为根的子树的话只要维护区间[dfs[u],dfs[u]+siz[u]-1]就好了):

      分两种情况:

        1、x、y在同一链上(链头相等),只要维护浅(深度小)的点的dfs序到另一个点的dfs序之间的区间就结束了

        2、x、y不在同一条链上,那么就让x或y往上跳,跳到同一个链上为止。跳那个呢?发现两点最终肯定不会同在x和y所在链的链头深度深的那个链,但有可能同在x和y所在链的链头深度浅的那个链,设这时dep[top[x]]>=dep[top[y]](如果假设不成立就交换x、y),把x跳到它的链头的父亲上,期间它会越过若干点,这若干点组成的dfs序的区间(包括x但不包括x跳完后所在的点)即为[dfs[top[x],dfs[x]],维护一下这个区间就好。以此规则不断跳,终能转化到第一种情况。

    查询与维护跳点的思路一致,将维护区间改为查询区间就好了。

    核心代码:

     1 int x,y,z;
     2 x=read(),y=read(),z=read();//将x到y路径所有点的点权加z 
     3 while(top[x]!=top[y])
     4 {
     5     if(dep[top[x]]<dep[top[y]])  swap(x,y);
     6     modify(1,1,n,dfs[top[x]],dfs[x],z);
     7     x=father[top[x]];
     8 }
     9 if(dep[x]>dep[y]) swap(x,y);
    10 modify(1,1,n,dfs[x],dfs[y],z);//线段树区间加 
    维护函数内部代码
     1 int x,y,z=0;//查询x到y路径所有点的点权之和 
     2 x=read(),y=read();
     3 while(top[x]!=top[y])
     4 {
     5     if(dep[top[x]]<dep[top[y]])  swap(x,y);
     6     z+=fin(1,1,n,dfs[top[x]],dfs[x]);
     7     x=father[top[x]];
     8 }
     9 if(dep[x]>dep[y]) swap(x,y);
    10 z+=fin(1,1,n,dfs[x],dfs[y]);
    11 printf("%d
    ",z%mod);
    查询函数内部代码

    完整代码:

      1 #include<iostream>
      2 #include<cstdio>
      3 #include<algorithm>
      4 
      5 using namespace std;
      6 
      7 const int N=100000,M=100000;
      8 
      9 int x,n,m,root,mod,num[N+5],tree[N<<2+5],dep[N+5],son[N+5],dfs[N+5],xu[N+5],top[N+5],siz[N+5];
     10 
     11 int father[N+5],lst[N+5],to[N*2],nxt[N*2],cnt,lzy[N<<2];
     12 
     13 char ch;
     14 
     15 inline int read()
     16 {
     17     x=0;
     18     ch=getchar();
     19     while(!isdigit(ch)) ch=getchar();
     20     while(isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
     21     return x;
     22 }
     23 
     24 inline void addedge(int u,int v)
     25 {
     26     nxt[++cnt]=lst[u];
     27     lst[u]=cnt;
     28     to[cnt]=v;
     29 }
     30 
     31 void dfs1(int u,int fa,int deep)
     32 {
     33     dep[u]=deep;
     34     siz[u]=1;
     35     int maxs=0,maxd=0;
     36     int t;
     37     for(int e=lst[u];e;e=nxt[e])
     38     {
     39         t=to[e];
     40         if(t!=fa)
     41         {
     42             father[t]=u;
     43             dfs1(t,u,deep+1);
     44             siz[u]+=siz[t];
     45             if(siz[t]>maxs)
     46             {
     47                 maxs=siz[t];
     48                 maxd=t;
     49             }
     50         }
     51     }
     52     son[u]=maxd;
     53 }
     54 
     55 int dfsxu;
     56 
     57 void dfs2(int u,int fa,int ding)
     58 {
     59     top[u]=ding;
     60     dfs[u]=++dfsxu;
     61     xu[dfsxu]=num[u];
     62     if(!son[u])
     63         return;
     64     dfs2(son[u],u,ding);
     65     int t;
     66     for(int e=lst[u];e;e=nxt[e])
     67     {
     68         t=to[e];
     69         if(t!=fa&&t!=son[u])
     70             dfs2(t,u,t);
     71     }
     72 }
     73 
     74 inline void updata(int t)
     75 {
     76     tree[t]=(tree[t<<1]+tree[t<<1|1])%mod;
     77 }
     78 
     79 void build(int t,int l,int r)
     80 {
     81     if(l==r)
     82     {
     83         tree[t]=xu[l]%mod;return;
     84     }
     85     build(t<<1,l,(l+r)>>1);
     86     build(t<<1|1,((l+r)>>1)+1,r);
     87     updata(t);
     88 }
     89 
     90 inline void init()
     91 {
     92     for(int i=1;i<=n;i++) num[i]=read();
     93     int u,v;
     94     for(int i=1;i<n;++i)
     95     {
     96         u=read(),v=read();
     97         addedge(u,v);
     98         addedge(v,u);
     99     }
    100     dfs1(root,-1,1);
    101     dfs2(root,-1,root);
    102     build(1,1,n);
    103 }
    104 
    105 inline void pushdown(int t,int l,int r)
    106 {
    107     if(!lzy[t]) return;
    108     lzy[t<<1]=(lzy[t<<1]+lzy[t])%mod;
    109     lzy[t<<1|1]=(lzy[t<<1|1]+lzy[t])%mod; 
    110     int mid=(l+r)>>1;
    111     tree[t<<1]=(tree[t<<1]+(mid-l+1)*lzy[t])%mod;
    112     tree[t<<1|1]=(tree[t<<1|1]+(r-mid)*lzy[t])%mod;
    113     lzy[t]=0;
    114 }
    115 
    116 void modify(int t,int l,int r,int x,int y,int a)
    117 {
    118     if(x<=l&&r<=y)
    119     {
    120         lzy[t]=(lzy[t]+a)%mod;
    121         tree[t]=(tree[t]+(r-l+1)*a)%mod;
    122         return;
    123     }
    124     pushdown(t,l,r);
    125     int mid=(l+r)>>1;
    126     if(x<=mid) modify(t<<1,l,mid,x,y,a);
    127     if(y>mid) modify(t<<1|1,mid+1,r,x,y,a);
    128     updata(t);
    129 }
    130 
    131 int fin(int t,int l,int r,int x,int y)
    132 {
    133     if(x<=l&&r<=y)
    134         return tree[t];
    135     pushdown(t,l,r);
    136     int mid=(l+r)>>1,ans=0;
    137     if(x<=mid) ans+=fin(t<<1,l,mid,x,y);
    138     if(y>mid) ans+=fin(t<<1|1,mid+1,r,x,y);
    139     return ans%mod;
    140 }
    141 
    142 inline void gan1()
    143 {
    144     int x,y,z;
    145     x=read(),y=read(),z=read();
    146     while(top[x]!=top[y])
    147     {
    148         if(dep[top[x]]<dep[top[y]])  swap(x,y);
    149         modify(1,1,n,dfs[top[x]],dfs[x],z);
    150         x=father[top[x]];
    151     }
    152     if(dep[x]>dep[y]) swap(x,y);
    153     modify(1,1,n,dfs[x],dfs[y],z);
    154 }
    155 
    156 inline void gan2()
    157 {
    158     int x,y,z=0;
    159     x=read(),y=read();
    160     while(top[x]!=top[y])
    161     {
    162         if(dep[top[x]]<dep[top[y]])  swap(x,y);
    163         z+=fin(1,1,n,dfs[top[x]],dfs[x]);
    164         x=father[top[x]];
    165     }
    166     if(dep[x]>dep[y]) swap(x,y);
    167     z+=fin(1,1,n,dfs[x],dfs[y]);
    168     printf("%d
    ",z%mod);
    169 }
    170 
    171 inline void solve()
    172 {
    173     int type,x,y;
    174     for(int i=1;i<=m;++i)
    175     {
    176         type=read();
    177         switch(type)
    178         {
    179             case 1:gan1();break;
    180             case 2:gan2();break;
    181             case 3:
    182                 x=read(),y=read();
    183                 modify(1,1,n,dfs[x],dfs[x]+siz[x]-1,y);
    184                 break;
    185             case 4:
    186                 x=read();
    187                 printf("%d
    ",fin(1,1,n,dfs[x],dfs[x]+siz[x]-1));
    188                 break;
    189         }
    190     }
    191 }
    192  
    193 int main()
    194 {
    195     n=read(),m=read(),root=read(),mod=read();
    196     init();
    197     solve();
    198     return 0;
    199 }
    洛谷树链剖分代码

    复杂度证明:

      设u通过一条轻边连接它的儿子v,易知size[u]/2 >=size[v],即每走一次轻边,当前子树的大小都至少减了一半,故根节点到任意一个叶子节点最多走log n条轻边,即最多经过log n条链。故单次路径查询时间复杂度为O(log n)。若单次修改的复杂度为log n(这里以线段树维护为例),则总时间复杂度为O(n log ^2 n) 

    模板题:洛谷P3384 【模板】树链剖分

    简单说下树链剖分的另一种做法:长链剖分,即进行分链时不再优先size最大的儿子,而是deep(深度)最大的儿子。

      有性质:

        1、任意点的任意祖先所在长链长度一定大于等于这个点所在长链长度

        2、所有长链长度之和就是总节点数

        3、一个点到根的路径上经过的短边最多有O(sqrt(n))条。

        证明下第三个性质:不放将重轻的说法改为类似的长短边。每向上走一个短边,当前子树的size至少加已走过的最长链的长度再加1,则当前子树大小>=1+2+。。。+k(k为走的短边数)=k*(k+1)/2,子树大小最大为n,则k最大为O(sqrt(n))。

        可见单次路径查询时间复杂度为O(sqrt(n)),其他情况与重链剖分类似。

    参考资料:

    树链剖分讲解及总结(重链剖分+长链剖分)

  • 相关阅读:
    centos7下安装Jdk8、Tomcat8
    CentOS7.3下使用YUM安装MySQL5.6
    数据库表直接生成Excel
    分页工具类
    ResponseUtil
    图片上传
    UUIDUtils
    html和js
    java
    Eclipse启动发生的错误An internal error occurred during: "Initializing Java Tooling".
  • 原文地址:https://www.cnblogs.com/InductiveSorting-QYF/p/11543839.html
Copyright © 2011-2022 走看看