推荐一个大佬的视频: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 }