zoukankan      html  css  js  c++  java
  • 树链剖分学习笔记 By cellur925

    先%一发机房各路祖传树剖大师%%%。

    近来总有人向我安利树剖求LCA,然鹅我还是最爱树上倍增。然鹅又发现近年一些题目(如天天爱跑步、运输计划等在树上进行操作的题目),我有把树转化为一条链求解的思路,但是不知道怎么实现。于是还是学了树链剖分(真香),就权当打暴力的工具了。其实主要是学习它的思想,而它实际包含的知识(线段树(大多情况用线段树,理论上应该还能用其他数据结构维护)、dfs序与时间戳、树的遍历)比较基础,只要把他们掌握,学习树剖就不难了。讲真树剖可能是我学的最快的知识


    主要思想:划分轻重链,把树上的某条路径化为一条链,再用数据结构维护。(把一棵树拆成若干互不相交的链)

    树剖中的一些概念:

      这里可详见@communist dalao的解释部分

    可见,树剖中我们需要记录很多的量,于是就有了我们的第一个核心算法:两遍dfs,求出所有信息!

    通常我们第一遍dfs求出d[](深度),f[](父节点),size[](子树大小),son[](重儿子)。

    void dfs1(int u,int fa,int dep)
    {//第一遍dfs 预处理出f[]/d[]/son[]/size[]
        f[u]=fa;
        d[u]=dep;
        size[u]=1;
        for(int i=head[u];i;i=edge[i].next)
        {
            int v=edge[i].to;
            if(v==fa) continue;
            dfs1(v,u,dep+1);
            size[u]+=size[v];
            if(size[v]>size[son[u]])
                son[u]=v;
        }
    }

    第二遍我们在第一遍的基础上继续求出其他量。top[](当前节点所在链的最顶层节点),id[](节点的新编号,可理解为时间戳),rk[](保存dfs标号在树上的具体节点,实际操作可为点权)

    这里id与rk的关系有点像时间戳与dfs序,但不完全是,emm可以感性理解下。(时间戳与dfs序,他们的联系具体在这里探讨过。)

    至于为什么要引入他们,因为我们希望一条重链在数据结构(如线段树)上的排列分布是连续的,这样我们才好维护他们。

    void dfs2(int u,int t)
    {//第二遍dfs 预处理出id[]/rk[]/top[] 
        top[u]=t;
        id[u]=++cnt;
        rk[cnt]=val[u];
        if(!son[u]) return ;//找到了叶子
        //通过优先进入重儿子来保证一条重链上各节点dfs序连续 
        dfs2(son[u],t);
        for(int i=head[u];i;i=edge[i].next)
        {
            int v=edge[i].to;
            if(v==son[u]||v==f[u]) continue;
            dfs2(v,v);//在轻链上 top为本身 
        } 
    } 

    有了这些操作,我们就可以进行线段树的维护了。这里的线段树中,节点标号用的是我们刚映射好的时间戳,权值是节点的点权,重链上的节点在线段树上标号连续。(注意我们的rk数组,建树时用的是它而不是初始点权。)

    之后我们就按线段树规矩建树、修改、查询即可。这是一个相对独立的部分。


    以上便是树链剖分的基本操作,我们以LuoguP3384【模板】树链剖分为例,讲解一下树剖的具体食用方法。

    题目要求我们维护:

    操作1: 格式: 1 x y z 表示将树从x到y结点最短路径上所有节点的值都加上z

    操作2: 格式: 2 x y 表示求树从x到y结点最短路径上所有节点的值之和

    操作3: 格式: 3 x z 表示将以x为根节点的子树内所有节点值都加上z

    操作4: 格式: 4 x 表示求以x为根节点的子树内所有节点值之和

    慢慢分析。在操作1中,我们首先需要让x,y到同一个重链上,但是在到达之前,我们也需要记录下。于是就有了

    void treeadd(int x,int y,int w)
    {
        while(top[x]!=top[y])
        {
            if(d[top[x]]<d[top[y]]) swap(x,y);
            change(1,id[top[x]],id[x],w);
            x=f[top[x]];
        }
        if(d[x]>d[y]) swap(x,y);
        change(1,id[x],id[y],w);
    }

    操作2原理相似。因为这部分我讲的不是很好==。我是看这位大神的blog看懂的,这里就甩链接了...。

    ll treeask(int x,int y)
    {
        ll ans=0;
        while(top[x]!=top[y])
        {
            if(d[top[x]]<d[top[y]]) swap(x,y);
            (ans+=ask(1,id[top[x]],id[x]))%=moder;
            x=f[top[x]];
        }
        if(d[x]>d[y]) swap(x,y);
        (ans+=ask(1,id[x],id[y]))%=moder;
        return ans;
    }

    至于操作3.4,其实最简单了qwq。我们直接在线段树上操作就行了。

    综上,我们解决了第一道树剖题。

      1 #include<cstdio>
      2 #include<algorithm>
      3 #define maxn 100090
      4 
      5 using namespace std;
      6 typedef long long ll;
      7 
      8 int n,m,root,moder,tot,cnt;
      9 int head[maxn],size[maxn],d[maxn],f[maxn],son[maxn],val[maxn];
     10 int top[maxn],id[maxn],rk[maxn];
     11 struct node{
     12     int to,next;
     13 }edge[maxn*2];
     14 struct SegmentTree{
     15     int l,r;
     16     ll w,lazy;
     17 }t[maxn*4];
     18 
     19 void add(int x,int y)
     20 {
     21     edge[++tot].to=y;
     22     edge[tot].next=head[x];
     23     head[x]=tot;
     24 }
     25 
     26 void dfs1(int u,int fa,int dep)
     27 {//第一遍dfs 预处理出f[]/d[]/son[]/size[]
     28     f[u]=fa;
     29     d[u]=dep;
     30     size[u]=1;
     31     for(int i=head[u];i;i=edge[i].next)
     32     {
     33         int v=edge[i].to;
     34         if(v==fa) continue;
     35         dfs1(v,u,dep+1);
     36         size[u]+=size[v];
     37         if(size[v]>size[son[u]])
     38             son[u]=v;
     39     }
     40 }
     41 
     42 void dfs2(int u,int t)
     43 {//第二遍dfs 预处理出id[]/rk[]/top[] 
     44     top[u]=t;
     45     id[u]=++cnt;
     46     rk[cnt]=val[u];
     47     if(!son[u]) return ;//找到了叶子
     48     //通过优先进入重儿子来保证一条重链上各节点dfs序连续 
     49     dfs2(son[u],t);
     50     for(int i=head[u];i;i=edge[i].next)
     51     {
     52         int v=edge[i].to;
     53         if(v==son[u]||v==f[u]) continue;
     54         dfs2(v,v);//在轻链上 top为本身 
     55     } 
     56 } 
     57 
     58 void update(int p)
     59 {
     60     if(t[p].l==t[p].r) return ;
     61     if(!t[p].lazy) return ;
     62     t[p*2].w+=t[p].lazy*(t[p*2].r-t[p*2].l+1);
     63     t[p*2+1].w+=t[p].lazy*(t[p*2+1].r-t[p*2+1].l+1);
     64     t[p*2].lazy+=t[p].lazy;
     65     t[p*2+1].lazy+=t[p].lazy;
     66     t[p].lazy=0;
     67 }
     68 
     69 void build(int p,int l,int r)
     70 {
     71     t[p].l=l,t[p].r=r;
     72     if(l==r)
     73     {
     74         t[p].w=rk[l];
     75         return ;
     76     }
     77     int mid=(l+r)>>1;
     78     build(p*2,l,mid);
     79     build(p*2+1,mid+1,r);
     80     (t[p].w=t[p*2].w+t[p*2+1].w)%=moder;
     81 }
     82 
     83 void change(int p,int l,int r,int x)
     84 {
     85     update(p);
     86     if(t[p].l==l&&t[p].r==r)
     87     {
     88         t[p].w+=x*(r-l+1);
     89         t[p].lazy+=x;
     90         return ;
     91     }
     92     int mid=(t[p].l+t[p].r)>>1;
     93     if(l>mid) change(p*2+1,l,r,x);
     94     else if(r<=mid) change(p*2,l,r,x);
     95     else change(p*2,l,mid,x),change(p*2+1,mid+1,r,x);
     96     (t[p].w=t[p*2].w+t[p*2+1].w)%=moder;
     97 }
     98 
     99 ll ask(int p,int l,int r)
    100 {
    101     update(p);
    102     if(t[p].l==l&&t[p].r==r) return (t[p].w)%moder;
    103     int mid=(t[p].l+t[p].r)>>1;
    104     if(l>mid) return (ask(p*2+1,l,r))%moder;
    105     else if(r<=mid) return (ask(p*2,l,r))%moder;
    106     else return (ask(p*2,l,mid)+ask(p*2+1,mid+1,r))%moder; 
    107 }
    108 
    109 ll treeask(int x,int y)
    110 {
    111     ll ans=0;
    112     while(top[x]!=top[y])
    113     {
    114         if(d[top[x]]<d[top[y]]) swap(x,y);
    115         (ans+=ask(1,id[top[x]],id[x]))%=moder;
    116         x=f[top[x]];
    117     }
    118     if(d[x]>d[y]) swap(x,y);
    119     (ans+=ask(1,id[x],id[y]))%=moder;
    120     return ans;
    121 }
    122 
    123 void treeadd(int x,int y,int w)
    124 {
    125     while(top[x]!=top[y])
    126     {
    127         if(d[top[x]]<d[top[y]]) swap(x,y);
    128         change(1,id[top[x]],id[x],w);
    129         x=f[top[x]];
    130     }
    131     if(d[x]>d[y]) swap(x,y);
    132     change(1,id[x],id[y],w);
    133 }
    134 
    135 int main()
    136 {
    137     scanf("%d%d%d%d",&n,&m,&root,&moder);
    138     for(int i=1;i<=n;i++) scanf("%d",&val[i]);
    139     for(int i=1;i<=n-1;i++)
    140     {
    141         int x=0,y=0;
    142         scanf("%d%d",&x,&y);
    143         add(x,y);add(y,x);
    144     }
    145     dfs1(root,0,1);
    146     dfs2(root,root); 
    147     build(1,1,n);
    148     while(m--)
    149     {
    150         int opt=0;
    151         scanf("%d",&opt);
    152         if(opt==1)
    153         {
    154             int x=0,y=0,z=0;
    155             scanf("%d%d%d",&x,&y,&z);
    156             treeadd(x,y,z);
    157         }
    158         else if(opt==2)
    159         {
    160             int x=0,y=0;
    161             scanf("%d%d",&x,&y);
    162             printf("%lld
    ",treeask(x,y));
    163         }
    164         else if(opt==3)
    165         {
    166             int x=0,y=0;
    167             scanf("%d%d",&x,&y);
    168             change(1,id[x],id[x]+size[x]-1,y);
    169         }
    170         else if(opt==4)
    171         {
    172             int x=0;
    173             scanf("%d",&x);
    174             printf("%lld
    ",ask(1,id[x],id[x]+size[x]-1)%moder);
    175         }
    176     }
    177     return 0;
    178 }
    View Code

    Update:2018/10/3

    早上来做了一道树链剖分...本想一次过的结果卡到九点...。

    原因:单点修改的时候用的是x而不是id[x],查询最大值的时候因为忽略了还有负数,所以初始值设的是0,而应该是负无穷。

    题目链接

    Update:2018/10/6

    今天又做了一道板子题==

    依然改了很久==

    没有什么困难的操作,有一个单点修改。

    开始我是想纯用之前的单点修改不带懒标记的,后来爆零了==

    改成区间修改左右端点相同的情况,再开着longlong就能A了==。

    百思不得其解,难道只能化为区间修改的特殊情况了么==。

    后来在金涛的指点下,发现单点修改下传一个懒标记就行了,就能A了。原因不太明...。

    Update:2018-10-31

    今日份敲模板的错误:

    //size[x]=1
    //chushizhi rk[p]->rk[l]
    //update 一直在lazy val操作假的

  • 相关阅读:
    分布式缓存负载均衡的规则处理:虚拟节点对一致性哈希的改进
    GoldenGate实时投递数据到大数据平台(6)– HDFS
    OGG 12.3中支持系统procedure复制的几点说明
    使用GoldenGate初始化的两种方式
    传统OGG与Microservice Architecture OGG的通信
    利用Oracle GoldenGate记录源系统所有表的操作
    GoldenGate实时投递数据到大数据平台(5)
    GoldenGate 12.2抽取Oracle 12c多租户配置过程
    GoldenGate实时投递数据到大数据平台(4)- ElasticSearch 2.x
    GoldenGate实时投递数据到大数据平台(3)- Apache Flume
  • 原文地址:https://www.cnblogs.com/nopartyfoucaodong/p/9738823.html
Copyright © 2011-2022 走看看