树链剖分学习笔记 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\n",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\n",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操作假的

posted @ 2018-10-02 23:07  cellur925&Chemist  阅读(178)  评论(0编辑  收藏  举报