树链剖分

树链剖分

十一月 03, 2021

树链剖分

要求

已知一棵包含 N 个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作:

  • 1 x y z,表示将树从$x$到$y$结点最短路径上所有节点的值都加上$z$。
  • 2 x y,表示求树从$x$到$y$结点最短路径上所有节点的值之和。
  • 3 x z,表示将以$x$为根节点的子树内所有节点值都加上$z$。
  • 4 x 表示求以$x$为根节点的子树内所有节点值之和

前置知识

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

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

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

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

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

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

思路框架

树链剖分,简称就是树剖,指的是通过把树剖成若干个链后选择用数据结构维护以实现树上的操作

树剖的实际上就是两遍预处理,第一步要算出以下数据:
对于任意节点u的深度子树大小重儿子编号父节点编号;分别记为:$\text{depth , size , son , fa}$这点用一个简单的dfs就可以实现,时间复杂度$O(n)$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void dfs1(int u,int fa){
fa[u]=fa;
size[u]=1;
dep[u]=dep[fa]+1;
int t=-1;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(v==fa) continue;
dfs1(v,u);
siz[u]+=siz[v];
if(siz[v]>t){
t=siz[v];
son[u]=v;
}
}
}

第二遍预处理,则需要计算:
对于任意节点u的所在重链顶点第几个被遍历,分别记为$\text{top , id}$ ,时间复杂度也是线性的

1
2
3
4
5
6
7
8
9
10
11
12
inline void dfs2(int u,int f){
top[u]=f;
id[u]=++tot;
val[tot]=w[u];
if(!son[u]) return;//指叶子节点
dfs2(son[u],f);
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(v==fa[u]||v==son[u]) continue;
dfs2(v,v);
}
}

接下来我们以$\text{dfn}$序排列的val值建树。

然后我们处理题目中的四个操作

我们要记住两个性质:

1、一条重链上的点$\text{id}$连续
2、一棵子树上的点$\text{id}$也连续

第一个我们是要在一条链上做加法,那么我们就是应该先去剖出这条链
第二个我们是要在在链上查询,和上一个大同小异

第三个是要在树上做加法
第四个我们是要在树上查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
inline void add_path(int u,int v,int k){
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]) swap(u,v);
upd_tree(id[top[u]],id[u],k,1,n,1);
u=fa[top[u]];
}
if(dep[u]>dep[v]) swap(u,v);
upd_tree(id[u],id[v],k,1,n,1);
}

inline int query_path(int u,int v){
int res=0;
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]) swap(u,v);
res+=query_tree(id[top[u]],id[u],1,n,1);
u=fa[top[u]];
}
if(dep[u]>dep[v]) swap(u,v);
res+=query_tree(id[u],id[v],1,n,1);
return res%mod;
}
inline void add_son(int u,int k){
upd_tree(id[u],id[u]+siz[u]-1,k,1,n,1);
}

inline int query_son(int u){
return query_tree(id[u],id[u]+siz[u]-1,1,n,1)%mod;
}

完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
inline int lc(int p){return p<<1;}
inline int rc(int p){return p<<1|1;}
const int N=1e6+10;

struct edge{
int nxt,to;
}e[N];
int head[N],cnt;
int dep[N],siz[N],fa[N],top[N],id[N],son[N];
long long tree[N<<2],tag[N<<2],w[N],val[N],mod;
int n,rt,T,tot;

inline void add(int u,int v){
e[++cnt].to=v;
e[cnt].nxt=head[u];
head[u]=cnt;
}

inline void dfs1(int u,int f){
fa[u]=f;
siz[u]=1;
dep[u]=dep[f]+1;
int t=-1;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(v==f) continue;
dfs1(v,u);
siz[u]+=siz[v];
if(siz[v]>t){
t=siz[v];
son[u]=v;
}
}
}

inline void dfs2(int u,int f){
top[u]=f;
id[u]=++tot;
val[tot]=w[u];
if(!son[u]) return;//指叶子节点
dfs2(son[u],f);
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(v==fa[u]||v==son[u]) continue;
dfs2(v,v);
}
}

//线段树操作----------------------------------------------------------------
inline void push_up(int p){
tree[p]=(tree[lc(p)]+tree[rc(p)])%mod;
}

inline void build(int l,int r,int p){
if(l==r){
tree[p]=val[l];
return;
}
int mid=(l+r)>>1;
build(l,mid,lc(p));
build(mid+1,r,rc(p));
push_up(p);
}

inline void pushdown(int l,int r,int p){
int mid=(l+r)>>1;
if(tag[p]){
tag[lc(p)]+=tag[p];
tag[rc(p)]+=tag[p];
tree[lc(p)]+=tag[p]*(mid-l+1);
tree[rc(p)]+=tag[p]*(r-mid);
tag[p]=0;
}
}

inline void upd_tree(int l,int r,int k,int m,int n,int p){
//把l到r这段区间全部加k,总区间为m,n
k%=mod;
if(l<=m&&n<=r){
tree[p]+=(n-m+1)*k;
tag[p]+=k;
return ;
}
int mid=(m+n)>>1;
pushdown(m,n,p);
if(l<=mid) upd_tree(l,r,k,m,mid,lc(p));
if(r>mid) upd_tree(l,r,k,mid+1,n,rc(p));
push_up(p);
}

inline int query_tree(int l,int r,int m,int n,int p){
if(l<=m&&n<=r) return tree[p];
int mid=(m+n)>>1;
pushdown(m,n,p);
int sum=0;
if(l<=mid) sum+=query_tree(l,r,m,mid,lc(p));
if(r>mid) sum+=query_tree(l,r,mid+1,n,rc(p));
return sum%mod;
}
//线段树操作-------------------------------------------------------------
//路径操作---------------------------------------------------------------
inline void add_path(int u,int v,int k){
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]) swap(u,v);
upd_tree(id[top[u]],id[u],k,1,n,1);
u=fa[top[u]];
}
if(dep[u]>dep[v]) swap(u,v);
upd_tree(id[u],id[v],k,1,n,1);
}

inline int query_path(int u,int v){
int res=0;
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]) swap(u,v);
res+=query_tree(id[top[u]],id[u],1,n,1);
u=fa[top[u]];
}
if(dep[u]>dep[v]) swap(u,v);
res+=query_tree(id[u],id[v],1,n,1);
return res%mod;
}
//路径操作----------------------------------------------------------
//树上操作----------------------------------------------------------
inline void add_son(int u,int k){
upd_tree(id[u],id[u]+siz[u]-1,k,1,n,1);
}

inline int query_son(int u){
return query_tree(id[u],id[u]+siz[u]-1,1,n,1)%mod;
}
//树上操作----------------------------------------------------------
signed main(){
cin>>n>>T>>rt>>mod;
for(int i=1;i<=n;i++) cin>>w[i],w[i]%=mod;
for(int i=1,u,v;i<n;i++) cin>>u>>v,add(u,v),add(v,u);
dfs1(rt,0);
dfs2(rt,rt);
build(1,n,1);

for(int i=1;i<=n;i++) cout<<top[i]<<" ";
cout<<endl;
for(int i=1;i<=n;i++) cout<<id[i]<<" ";
cout<<endl;

while(T--){
int x,y,op,k;
cin>>op>>x;
if(op==1) cin>>y>>k, add_path(x,y,k);
if(op==2) cin>>y, cout<<query_path(x,y)<<endl;
if(op==3) cin>>k, add_son(x,k);
if(op==4) cout<<query_son(x)<<endl;
}
return 0;
}

/*
8 10 2 448348
458 718 447 857 633 264 238 944
1 2
2 3
3 4
6 2
1 5
5 7
8 6
3 7 611
4 6
3 1 267
3 2 111
1 6 3 153
3 7 673
4 8
2 6 1
4 7
3 4 228

*/