树链剖分入门(附代码详细注释)

 推荐一个大佬的视频:here

模板题:P3384 【模板】轻重链剖分

 

 模板:

  1 #include <bits/stdc++.h>
  2 typedef long long ll;
  3 using namespace std;
  4 const int maxn = 1e5+5;
  5 
  6 //dfs序可以让一些链上的节点在一个连续的区间
  7 //树链剖分则是强制地让重链上的节点在一个连续的区间
  8 //重链剖分不改变dfs序的性质:一棵子树所在的位置处于一个连续区间中
  9 
 10 
 11 struct node{
 12     int to,nxt;
 13 }e[maxn<<1];
 14 
 15 struct Tree{
 16     int l,r,f,val;//区间左边界,右边界,lazy标记,区间和
 17 }tree[maxn<<2];
 18 
 19 int tot,head[maxn];
 20 
 21 int n,m,r,mod;
 22 int v[maxn];
 23 
 24 void init(){
 25     memset(head,-1,sizeof(head));
 26     tot=0;
 27 }
 28 
 29 void addedge(int u,int v){
 30     e[tot].to=v; e[tot].nxt=head[u]; head[u]=tot++;
 31 }
 32 
 33 int fa[maxn],dep[maxn],siz[maxn],son[maxn];
 34 void dfs1(int u,int f){
 35     fa[u] = f;
 36     dep[u] = dep[f]+1;
 37     siz[u] = 1;
 38     int maxsize = -1;   //判断重儿子的变量
 39     for(int i=head[u];~i;i=e[i].nxt){
 40         int v=e[i].to;
 41         if( v==f ) continue;
 42         dfs1(v,u);
 43         siz[u] += siz[v];
 44         if( siz[v]>maxsize ){
 45             maxsize = siz[v];
 46             son[u] = v; //son存重儿子
 47         }
 48     }
 49 }
 50 
 51 //计数器, dfs序时间戳, 重链的头, 节点权值的dfs序
 52 int tim, dfn[maxn],top[maxn],w[maxn];
 53 void dfs2(int u,int t){
 54     dfn[u] = ++tim;
 55     top[u] = t;
 56     w[tim] = v[u];
 57     if( !son[u] ) return ;//这个节点没有重儿子,就是叶子结点
 58     dfs2(son[u],t); //先往重儿子走,
 59     for(int i=head[u];~i;i=e[i].nxt){   //跑轻儿子
 60         int v = e[i].to;
 61         if( v==fa[u] || v==son[u] ) continue;
 62         dfs2(v,v);  //新开始一条重链,头是自己本身
 63     }
 64 }
 65 
 66 void pushup(int rt){//裸线段树向上维护区间和
 67     tree[rt].val = (tree[rt<<1].val + tree[rt<<1|1].val)%mod;
 68 }
 69 
 70 void pushdown(int rt){//根据lazy标记下放
 71     tree[rt<<1].f += tree[rt].f;
 72     tree[rt<<1|1].f += tree[rt].f;
 73     tree[rt<<1].val += (tree[rt<<1].r-tree[rt<<1].l+1)*tree[rt].f%mod;
 74     tree[rt<<1|1].val += (tree[rt<<1|1].r-tree[rt<<1|1].l+1) * tree[rt].f %mod;
 75     tree[rt].f = 0;
 76 }
 77 
 78 void build(int l,int r,int rt=1){//裸线段树建树
 79     tree[rt].l=l, tree[rt].r=r;
 80     if( l==r ){
 81         tree[rt].val = w[l]%mod;
 82         return ;
 83     }
 84     int mid = (l+r)>>1;
 85     build(l,mid,rt<<1);
 86     build(mid+1,r,rt<<1|1);
 87     pushup(rt);
 88 }
 89 
 90 void modify(int x, int y, int z, int rt=1){//线段树修改
 91     int l=tree[rt].l, r=tree[rt].r;
 92     if( x<=l && y>=r ){
 93         tree[rt].f += z;
 94         tree[rt].val += (r-l+1)*z;
 95         tree[rt].val %= mod;
 96         return ;
 97     }
 98     if( tree[rt].f ){
 99         pushdown(rt);
100     }
101     int mid = (l+r)>>1;
102     if( x<=mid ) modify(x,y,z,rt<<1);
103     if( y>mid ) modify(x,y,z,rt<<1|1);
104     pushup(rt);
105 }
106 
107 int query(int x,int y,int rt=1){   //线段树区间查询
108     int l=tree[rt].l, r=tree[rt].r;
109     if( x<=l && y>=r ){
110         return tree[rt].val;
111     }
112     if( tree[rt].f ) pushdown(rt);
113     int sum=0, mid=(l+r)>>1;
114     if( x<=mid ) sum += query(x,y,rt<<1);
115     if( y>mid ) sum += query(x,y,rt<<1|1);
116     return sum%mod;
117 }
118 
119 //重要引理:除根节点以外的任何一个结点的父亲结点都一定在一条重链上
120 //证明:因为父亲结点存在儿子,所以一定存在重儿子,所以一定在一条重链上
121 void mchain(int x,int y,int z){
122     z %= mod;
123     while( top[x]!=top[y] ){
124         if( dep[top[x]]<dep[top[y]] ){//注意这里是top[x],top[y],不是直接的x,y
125             swap(x,y);  //有点类似LCA那里
126         }
127         modify(dfn[top[x]], dfn[x], z); //区间修改
128         x = fa[top[x]]; //到这条链的头的父亲节点(看引理)
129     }
130 
131     if( dep[x]>dep[y] ){
132         swap(x,y);
133     }
134     modify(dfn[x], dfn[y], z);  //现在在一条链上了,所以是x,y,周时候就不要调到链的头上了,把x,y跳到一起就好了
135 }
136 
137 int qchain(int x,int y){
138     int ret=0;
139     while( top[x]!=top[y] ){
140         if( dep[top[x]]<dep[top[y]] ) swap(x,y);
141         ret += query(dfn[top[x]],dfn[x]);
142         x = fa[top[x]];
143     }
144     if( dep[x]>dep[y] ) swap(x,y);
145     ret += query(dfn[x],dfn[y]);
146     return ret % mod;
147 }
148 
149 void mson(int x,int z){
150     modify(dfn[x], dfn[x]+siz[x]-1,z);//依据dfs序的性质
151 }
152 
153 int qson(int x){
154     return query(dfn[x], dfn[x]+siz[x]-1);//依据dfs序的性质
155 }
156 
157 int main()
158 {
159     init();
160     scanf("%d%d%d%d",&n,&m,&r,&mod);
161     for(int i=1;i<=n;i++) scanf("%d",&v[i]);
162     for(int i=1;i<n;i++){
163         int u,v; scanf("%d%d",&u,&v);
164         addedge(u,v); addedge(v,u);
165     }
166     dfs1(r,r);
167     dfs2(r,r);
168     //注意转换思维当处理完dfs序与重链剖分后,就变换成数组来做题
169     build(1,n);//根据dfs序【就是一个数组】建立裸的线段树
170     while( m-- ){
171         int opt,x,y,z;
172         scanf("%d",&opt);
173         switch(opt){
174         case 1:
175             scanf("%d%d%d",&x,&y,&z);   //1 x y z 表示将树从 x 到 y 结点最短路径上所有节点的值都加上 z
176             mchain(x,y,z);
177             break;
178         case 2:
179             scanf("%d%d",&x,&y);    //2 x y 表示求树从 x 到 y 结点最短路径上所有节点的值之和
180             printf("%d
",qchain(x,y));
181             break;
182         case 3:
183             scanf("%d%d",&x,&z);    //3 x z 表示将以 x 为根节点的子树内所有节点值都加上 z
184             mson(x,z);
185             break;
186         case 4:
187             scanf("%d",&x);  //4 x 表示求以 x 为根节点的子树内所有节点值之和
188             printf("%d
",qson(x));
189             break;
190         }
191     }
192     return 0;
193 }
原文地址:https://www.cnblogs.com/wsy107316/p/13791001.html