DFS序和树链剖分都是在树的前提下才有效的。
DFS序
DFS,即对树进行DFS时遍历的顺序。记录每个点被遍历之前遍历过的点数
i
n
in
in 和遍历完以该点为根的子树后遍历过的点数
o
u
t
out
out。若点
v
v
v 为
u
u
u 子树中的点,则一定满足
i
n
u
⩽
i
n
v
⩽
o
u
t
u
in_u \leqslant in_v \leqslant out_u
inu⩽inv⩽outu。因此任意一点
u
u
u 的子树可以用区间
[
i
n
u
,
o
u
t
u
]
[in_u,out_u]
[inu,outu] 来表示。
求DFS序模板:
vector<int>G[100];
int ct,in[100],out[100];
void DFS(int x){
ct++;
in[x]=ct;
for(vector<int>::iterator T=G[x].begin();T!=G[x].end();T++){
DFS(*T);
}
out[x]=ct;
}
点修改,子树查询
对于点
u
u
u 的修改,只需要在
i
n
u
in_u
inu 处单点修改。对于子树
u
u
u 的查询,即对区间
[
i
n
u
,
o
u
t
u
]
[in_u,out_u]
[inu,outu] 内的点权求和。因此使用树状数组优化修改与查询。
时间复杂度
O
(
n
+
q
l
o
g
2
n
)
O(n+qlog_2n)
O(n+qlog2n),空间复杂度
O
(
n
)
O(n)
O(n)。
#include<stdio.h>
#define R register int
#define L long long
#define I inline
#define N 1000001
char BF[N],*p1,*p2;
#define GC (p1==p2&&(p2=(p1=BF)+fread(BF,1,N,stdin),p1==p2)?0:*p1++)
I void Read(int&x){
char t=GC;
x=0;
while(t<48||t>57){
t=GC;
}
while(t>47&&t<58){
x=(x<<3)+(x<<1)+(t^48);
t=GC;
}
}
int in[N],out[N],Last[N],End[N],Next[N],t[N],ct;
I void Link(int&x,int&y){
ct++;
End[ct]=y;
Next[ct]=Last[x];
Last[x]=ct;
}
I void DFS(int x){
ct++;
in[x]=ct;
for(R i=Last[x];i!=0;i=Next[i]){
DFS(End[i]);
}
out[x]=ct;
}
L c[N];
I void Add(int x,int d){
for(R i=x;i<N;i+=i&-i){
c[i]+=d;
}
}
I L Get(int x){
L s=0;
for(R i=x;i!=0;i-=i&-i){
s+=c[i];
}
return s;
}
int main(){
int m,a,b,n;
Read(n);
Read(m);
for(R i=1;i<=n;i++){
Read(t[i]);
}
for(R i=2;i<=n;i++){
Read(a);
Link(a,i);
}
ct=0;
DFS(1);
for(R i=1;i<=n;i++){
Add(in[i],t[i]);
}
for(R i=0;i!=m;i++){
Read(b);
Read(a);
if(b==1){
Read(b);
Add(in[a],b-t[a]);
t[a]=b;
}else{
printf("%lld\n",Get(out[a])-Get(in[a]-1));
}
}
return 0;
}
子树修改,点查询
对
u
u
u 子树修改即对区间
[
i
n
u
,
o
u
t
u
]
[in_u,out_u]
[inu,outu] 区间修改,改为差分,对点
i
n
u
in_u
inu 和
o
u
t
u
+
1
out_u+1
outu+1 的单点修改。单点查询也就改为了前缀查询。同样使用树状数组。
时间复杂度
O
(
n
+
q
l
o
g
2
n
)
O(n+qlog_2n)
O(n+qlog2n),空间复杂度
O
(
n
)
O(n)
O(n)。
#include<stdio.h>
#include<vector>
using namespace std;
#define R register int
#define L long long
#define I inline
#define N 1000001
char BF[N],*p1,*p2;
#define GC (p1==p2&&(p2=(p1=BF)+fread(BF,1,N,stdin),p1==p2)?0:*p1++)
I void Read(int&x){
char t=GC;
x=0;
bool tag=false;
while(t<48||t>57){
if(t=='-'){
tag=true;
}
t=GC;
}
while(t>47&&t<58){
x=(x<<3)+(x<<1)+(t^48);
t=GC;
}
if(tag==true){
x=-x;
}
}
int in[N],out[N],Last[N],Next[N],t[N],ct;
I void Link(int x,int y){
Next[y]=Last[x];
Last[x]=y;
}
I void DFS(int x){
ct++;
in[x]=ct;
for(R i=Last[x];i!=0;i=Next[i]){
DFS(i);
}
out[x]=ct;
}
L c[N];
I void Modify(int x,const int d){
for(R i=x;i<N;i+=i&-i){
c[i]+=d;
}
}
I L GetSum(int x){
L res=0;
for(R i=x;i!=0;i&=i-1){
res+=c[i];
}
return res;
}
int main(){
int n,q,opt,a,b;
Read(n);
Read(q);
for(R i=1;i<=n;i++){
Read(t[i]);
}
for(int i=2;i<=n;i++){
Read(a);
Link(a,i);
}
DFS(1);
for(R i=0;i!=q;i++){
Read(opt);
Read(a);
if(opt==1){
Read(b);
Modify(in[a],b);
Modify(out[a]+1,-b);
}else{
printf("%lld\n",GetSum(in[a])+t[a]);
}
}
return 0;
}
子树修改,子树查询
显然就是对区间修改,区间查询。用树状数组即可。
时间复杂度
O
(
n
+
q
l
o
g
2
n
)
O(n+qlog_2n)
O(n+qlog2n),空间复杂度
O
(
n
)
O(n)
O(n)。
#include<stdio.h>
#include<vector>
using namespace std;
#define R register int
#define L long long
#define I inline
#define N 1000001
char BF[N],*p1,*p2;
#define GC (p1==p2&&(p2=(p1=BF)+fread(BF,1,N,stdin),p1==p2)?0:*p1++)
I void Read(int&x){
char t=GC;
x=0;
bool tag=false;
while(t<48||t>57){
if(t=='-'){
tag=true;
}
t=GC;
}
while(t>47&&t<58){
x=(x<<3)+(x<<1)+(t^48);
t=GC;
}
if(tag==true){
x=-x;
}
}
int in[N],out[N],Last[N],Next[N],t[N],pos[N],ct;
I void Link(int x,int y){
Next[y]=Last[x];
Last[x]=y;
}
I void DFS(int x){
ct++;
in[x]=ct;
pos[ct]=x;
for(R i=Last[x];i!=0;i=Next[i]){
DFS(i);
}
out[x]=ct;
}
L c[N][2],sum[N];
I void Modify(int x,const int d){
for(R i=x;i<N;i+=i&-i){
c[i][0]+=d;
c[i][1]+=(L)d*x;
}
}
I L GetSum(int x){
L res1=0,res2=0;
for(R i=x;i!=0;i&=i-1){
res1+=c[i][0];
res2+=c[i][1];
}
return res1*(1+x)-res2;
}
int main(){
int n,q,opt,a,b;
Read(n);
Read(q);
for(R i=1;i<=n;i++){
Read(t[i]);
}
for(int i=2;i<=n;i++){
Read(a);
Link(a,i);
}
DFS(1);
for(R i=1;i<=n;i++){
sum[i]=sum[i-1]+t[pos[i]];
}
for(R i=0;i!=q;i++){
Read(opt);
Read(a);
if(opt==1){
Read(b);
Modify(in[a],b);
Modify(out[a]+1,-b);
}else{
printf("%lld\n",GetSum(out[a])-GetSum(in[a]-1)+sum[out[a]]-sum[in[a]-1]);
}
}
return 0;
}
树链剖分
树链剖分,即将树剖分成若干条链,这里讲的树链剖分为重链剖分。
将每个点的儿子分为两类,子树大小最大的重儿子和其他轻儿子。由重儿子们连成的链称为重链,轻儿子到其父节点的边称为轻边。显然一个节点到根节点的路径上会交替经过若干条重链和轻边。在一棵
n
n
n 个节点的树上,从一个点到根节点的路径上每经过一条轻边,由于目前在轻儿子,所以就会使目前到达的点的子树大小至少翻一倍。因此一条树链上只会有
O
(
l
o
g
2
n
)
O(log_2n)
O(log2n) 条重链。对于每个点,记录点的深度、子树大小、父节点、重儿子、所在重链的顶部即可。
预处理时间复杂度
O
(
n
)
O(n)
O(n)。
树链剖分预处理代码:
int h[N],sz[N],dep[N],f[N],Top[N];
vector<int>G[N];
void PreDFS(int x){
dep[x]=dep[f[x]]+1;
sz[x]=1;
for(vector<int>::iterator T=G[x].begin();T!=G[x].end();T++){
PreDFS(*T);
if(sz[*T]>sz[h[x]]){
h[x]=*T;
}
}
}
void ReDFS(int x,int t){
Top[x]=t;
if(h[x]!=0){
ReDFS(h[x],t);
for(vector<int>::iterator T=G[x].begin();T!=G[x].end();T++){
if(*T!=h[x]){
ReDFS(*T,*T);
}
}
}
}
求最近公共祖先
求任意两点的最近公共祖先,先判断两点是否在同一条重链上,即判断所在重链顶是否相同。否则,将重链顶深度更深的点移至重链顶的父节点,然后循环下去。
时间复杂度
O
(
l
o
g
2
n
)
O(log_2n)
O(log2n)。
求最近公共祖先模板:
int GetLCA(int x,int y){
while(Top[x]!=Top[y]){
if(dep[Top[x]]>dep[Top[y]]){
x=f[Top[x]];
}else{
y=f[Top[y]];
}
}
return dep[x]<dep[y]?x:y;
}
链修改,点查询
树链
x
x
x 到
y
y
y 的修改,可以等价为点
x
x
x、
y
y
y、
l
c
a
x
,
y
lca_{x,y}
lcax,y、
l
c
a
x
,
y
lca_{x,y}
lcax,y 父节点到根的链的四次修改。将点到根链的修改改为单点修改,单点查询改为子树查询。在树链剖分的基础上加上DFS序,并用树状数组优化。
时间复杂度
O
(
n
+
q
l
o
g
2
n
)
O(n+qlog_2n)
O(n+qlog2n),空间复杂度
O
(
n
)
O(n)
O(n)。
#include<stdio.h>
#include<vector>
using namespace std;
#define R register int
#define L long long
#define I inline
#define N 1000001
char BF[N],*p1,*p2;
#define GC (p1==p2&&(p2=(p1=BF)+fread(BF,1,N,stdin),p1==p2)?0:*p1++)
I void Read(int&x){
char t=GC;
x=0;
bool tag=false;
while(t<48||t>57){
if(t=='-'){
tag=true;
}
t=GC;
}
while(t>47&&t<58){
x=(x<<3)+(x<<1)+(t^48);
t=GC;
}
if(tag==true){
x=-x;
}
}
int t[N],h[N],sz[N],dep[N],f[N],in[N],out[N],Top[N],ct;
vector<int>G[N];
I void PreDFS(int x){
dep[x]=dep[f[x]]+1;
sz[x]=1;
for(vector<int>::iterator T=G[x].begin();T!=G[x].end();T++){
PreDFS(*T);
if(sz[*T]>sz[h[x]]){
h[x]=*T;
}
}
}
I void ReDFS(int x,int t){
Top[x]=t;
ct++;
in[x]=ct;
if(h[x]!=0){
ReDFS(h[x],t);
for(vector<int>::iterator T=G[x].begin();T!=G[x].end();T++){
if(*T!=h[x]){
ReDFS(*T,*T);
}
}
}
out[x]=ct;
}
I int GetLCA(int x,int y){
while(Top[x]!=Top[y]){
if(dep[Top[x]]>dep[Top[y]]){
x=f[Top[x]];
}else{
y=f[Top[y]];
}
}
return dep[x]<dep[y]?x:y;
}
L c[N];
I void Modify(int x,const int d){
for(R i=x;i<N;i+=i&-i){
c[i]+=d;
}
}
I L GetSum(int x){
L res=0;
for(R i=x;i!=0;i&=i-1){
res+=c[i];
}
return res;
}
int main(){
int n,q;
Read(n);
Read(q);
for(R i=1;i<=n;i++){
Read(t[i]);
}
for(int i=2;i<=n;i++){
Read(f[i]);
G[f[i]].push_back(i);
}
PreDFS(1);
ReDFS(1,1);
for(R i=0;i!=q;i++){
int opt,a;
Read(opt);
Read(a);
if(opt==1){
int b,c,lca;
Read(b);
Read(c);
lca=GetLCA(a,b);
Modify(in[a],c);
Modify(in[b],c);
Modify(in[lca],-c);
lca=f[lca];
if(lca!=0){
Modify(in[lca],-c);
}
}else{
printf("%lld\n",GetSum(out[a])-GetSum(in[a]-1)+t[a]);
}
}
return 0;
}
点修改,链查询
同样地,将树链改为四条点到根的链。点修改,其实是对子树内的点到根的链的和的修改。链查询改为点查询。还是使用树状数组。
时间复杂度
O
(
n
+
q
l
o
g
2
n
)
O(n+qlog_2n)
O(n+qlog2n),空间复杂度
O
(
n
)
O(n)
O(n)。
#include<stdio.h>
#include<vector>
using namespace std;
#define R register int
#define L long long
#define I inline
#define N 1000001
char BF[N],*p1,*p2;
#define GC (p1==p2&&(p2=(p1=BF)+fread(BF,1,N,stdin),p1==p2)?0:*p1++)
I void Read(int&x){
char t=GC;
x=0;
bool tag=false;
while(t<48||t>57){
if(t=='-'){
tag=true;
}
t=GC;
}
while(t>47&&t<58){
x=(x<<3)+(x<<1)+(t^48);
t=GC;
}
if(tag==true){
x=-x;
}
}
vector<int>G[N];
int t[N],h[N],sz[N],dep[N],f[N],in[N],out[N],Top[N],ct;
L c[N],dis[N];
I void PreDFS(int x){
dep[x]=dep[f[x]]+1;
dis[x]=dis[f[x]]+t[x];
sz[x]=1;
for(vector<int>::iterator T=G[x].begin();T!=G[x].end();T++){
PreDFS(*T);
if(sz[*T]>sz[h[x]]){
h[x]=*T;
}
}
}
I void ReDFS(int x,int t){
Top[x]=t;
ct++;
in[x]=ct;
if(h[x]!=0){
ReDFS(h[x],t);
for(vector<int>::iterator T=G[x].begin();T!=G[x].end();T++){
if(*T!=h[x]){
ReDFS(*T,*T);
}
}
}
out[x]=ct;
}
I int GetLCA(int x,int y){
while(Top[x]!=Top[y]){
if(dep[Top[x]]>dep[Top[y]]){
x=f[Top[x]];
}else{
y=f[Top[y]];
}
}
return dep[x]<dep[y]?x:y;
}
I void Modify(int x,const int d){
for(R i=x;i<N;i+=i&-i){
c[i]+=d;
}
}
I L GetSum(int x){
L res=0;
for(R i=x;i!=0;i&=i-1){
res+=c[i];
}
return res;
}
int main(){
int n,q;
Read(n);
Read(q);
for(R i=1;i<=n;i++){
Read(t[i]);
}
for(int i=2;i<=n;i++){
Read(f[i]);
G[f[i]].push_back(i);
}
PreDFS(1);
ReDFS(1,1);
for(R i=0;i!=q;i++){
int opt,a,b;
Read(opt);
Read(a);
Read(b);
if(opt==1){
Modify(in[a],b);
Modify(out[a]+1,-b);
}else{
int lca=GetLCA(a,b);
printf("%lld\n",GetSum(in[a])+GetSum(in[b])-GetSum(in[lca])-GetSum(in[f[lca]])+dis[a]+dis[b]-dis[lca]-dis[f[lca]]);
}
}
return 0;
}
链修改,子树查询
链修改,还是改为四次单点修改。修改某点到根的链时,会对该点到根路径上的所有点答案造成影响。具体地说来,若将点
i
i
i 到根的路径上所有点增加了
d
i
d_i
di,则点
u
u
u 子树的和增加量为
∑
v
d
v
(
d
e
p
v
−
d
e
p
u
+
1
)
\sum_v d_v(dep_v-dep_u+1)
∑vdv(depv−depu+1),其中点
v
v
v 在
u
u
u 子树中。变形得到
∑
v
d
v
d
e
p
v
−
(
d
e
p
u
−
1
)
∑
v
d
v
\sum_v d_v dep_v-(dep_u-1)\sum_{v}d_v
∑vdvdepv−(depu−1)∑vdv。因此树状数组维护
d
d
d 的线性和和
d
⋅
d
e
p
d·dep
d⋅dep 的和。
时间复杂度
O
(
n
+
q
l
o
g
2
n
)
O(n+qlog_2n)
O(n+qlog2n),空间复杂度
O
(
n
)
O(n)
O(n)。
#include<stdio.h>
#include<vector>
using namespace std;
#define R register int
#define L long long
#define I inline
#define N 1000001
char BF[N],*p1,*p2;
#define GC (p1==p2&&(p2=(p1=BF)+fread(BF,1,N,stdin),p1==p2)?0:*p1++)
I void Read(int&x){
char t=GC;
x=0;
bool tag=false;
while(t<48||t>57){
if(t=='-'){
tag=true;
}
t=GC;
}
while(t>47&&t<58){
x=(x<<3)+(x<<1)+(t^48);
t=GC;
}
if(tag==true){
x=-x;
}
}
vector<int>G[N];
int h[N],dep[N],t[N],in[N],out[N],f[N],Top[N],sz[N],pos[N],ct;
L sum[N],c1[N],c2[N];
I void PreDFS(int x){
dep[x]=dep[f[x]]+1;
sz[x]=1;
for(vector<int>::iterator T=G[x].begin();T!=G[x].end();T++){
PreDFS(*T);
sz[x]+=sz[*T];
if(sz[*T]>sz[h[x]]){
h[x]=*T;
}
}
}
I void ReDFS(int x){
ct++;
in[x]=ct;
pos[ct]=x;
if(h[x]!=0){
Top[h[x]]=Top[x];
ReDFS(h[x]);
for(vector<int>::iterator T=G[x].begin();T!=G[x].end();T++){
if(*T!=h[x]){
Top[*T]=*T;
ReDFS(*T);
}
}
}
out[x]=ct;
}
I int GetLCA(int x,int y){
while(Top[x]!=Top[y]){
if(dep[Top[x]]>dep[Top[y]]){
x=f[Top[x]];
}else{
y=f[Top[y]];
}
}
return dep[x]<dep[y]?x:y;
}
I void Modify(int x,const int d){
L tem=(L)d*dep[x];
for(R i=in[x];i<N;i+=i&-i){
c1[i]+=d;
c2[i]+=tem;
}
}
I L GetAns(int x){
L res1=0,res2=0;
for(R i=out[x];i!=0;i&=i-1){
res1+=c1[i];
res2+=c2[i];
}
for(R i=in[x]-1;i!=0;i&=i-1){
res1-=c1[i];
res2-=c2[i];
}
return res2-res1*(dep[x]-1);
}
int main(){
int n,q,opt,a,b,c,lca;
Read(n);
Read(q);
for(R i=1;i<=n;i++){
Read(t[i]);
}
for(int i=2;i<=n;i++){
Read(f[i]);
G[f[i]].push_back(i);
}
PreDFS(1);
Top[1]=1;
ReDFS(1);
for(R i=1;i<=n;i++){
sum[i]=sum[i-1]+t[pos[i]];
}
for(R i=0;i!=q;i++){
Read(opt);
Read(a);
if(opt==1){
Read(b);
Read(c);
lca=GetLCA(a,b);
Modify(a,c);
Modify(b,c);
Modify(lca,-c);
if(lca!=1){
Modify(f[lca],-c);
}
}else{
printf("%lld\n",GetAns(a)+sum[out[a]]-sum[in[a]-1]);
}
}
return 0;
}
子树修改,链查询
与前一题同理,子树修改后只会影响子树内的点到根的和。用树状数组维护
d
d
d 的线性和和
d
⋅
d
e
p
d·dep
d⋅dep 的和。
时间复杂度
O
(
n
+
q
l
o
g
2
n
)
O(n+qlog_2n)
O(n+qlog2n),空间复杂度
O
(
n
)
O(n)
O(n)。
#include<stdio.h>
#include<vector>
using namespace std;
#define R register int
#define L long long
#define I inline
#define N 1000001
char BF[N],*p1,*p2;
#define GC (p1==p2&&(p2=(p1=BF)+fread(BF,1,N,stdin),p1==p2)?0:*p1++)
I void Read(int&x){
char t=GC;
x=0;
bool tag=false;
while(t<48||t>57){
if(t=='-'){
tag=true;
}
t=GC;
}
while(t>47&&t<58){
x=(x<<3)+(x<<1)+(t^48);
t=GC;
}
if(tag==true){
x=-x;
}
}
vector<int>G[N];
int h[N],dep[N],t[N],in[N],out[N],f[N],Top[N],sz[N],pos[N],ct;
L sum[N],c1[N],c2[N];
I void PreDFS(int x){
dep[x]=dep[f[x]]+1;
sum[x]=sum[f[x]]+t[x];
sz[x]=1;
for(vector<int>::iterator T=G[x].begin();T!=G[x].end();T++){
PreDFS(*T);
sz[x]+=sz[*T];
if(sz[*T]>sz[h[x]]){
h[x]=*T;
}
}
}
I void ReDFS(int x){
ct++;
in[x]=ct;
pos[ct]=x;
if(h[x]!=0){
Top[h[x]]=Top[x];
ReDFS(h[x]);
for(vector<int>::iterator T=G[x].begin();T!=G[x].end();T++){
if(*T!=h[x]){
Top[*T]=*T;
ReDFS(*T);
}
}
}
out[x]=ct;
}
I int GetLCA(int x,int y){
while(Top[x]!=Top[y]){
if(dep[Top[x]]>dep[Top[y]]){
x=f[Top[x]];
}else{
y=f[Top[y]];
}
}
return dep[x]<dep[y]?x:y;
}
I void Modify(int x,const int d){
L tem=(L)d*dep[x];
for(R i=in[x];i<N;i+=i&-i){
c1[i]+=d;
c2[i]+=tem;
}
for(R i=out[x]+1;i<N;i+=i&-i){
c1[i]-=d;
c2[i]-=tem;
}
}
I L GetAns(int x){
L res1=0,res2=0;
for(R i=in[x];i!=0;i&=i-1){
res1+=c1[i];
res2+=c2[i];
}
return res1*(dep[x]+1)-res2+sum[x];
}
int main(){
int n,q,opt,a,b,lca;
Read(n);
Read(q);
for(R i=1;i<=n;i++){
Read(t[i]);
}
for(int i=2;i<=n;i++){
Read(f[i]);
G[f[i]].push_back(i);
}
PreDFS(1);
Top[1]=1;
ReDFS(1);
for(R i=0;i!=q;i++){
Read(opt);
Read(a);
Read(b);
if(opt==1){
Modify(a,b);
}else{
lca=GetLCA(a,b);
printf("%lld\n",GetAns(a)+GetAns(b)-GetAns(lca)-GetAns(f[lca]));
}
}
return 0;
}
树链剖分除了求最近公共祖先以外,还可以结合数据结构,用数据结构维护重链,以达到快速修改、查询的目的。
链修改,链查询
树链可以被分成许多重链,对每个重链用树状数组维护前缀和,每部分在重链上都是区间查询、区间修改。
时间复杂度
O
(
n
+
q
l
o
g
2
n
2
)
O(n+qlog_2n^2)
O(n+qlog2n2),空间复杂度
O
(
n
)
O(n)
O(n)。
#include<stdio.h>
#include<vector>
using namespace std;
#define R register int
#define L long long
#define I inline
#define N 1000001
I void Swap(int&x,int&y){
int tem=x;
x=y;
y=tem;
}
char BF[N],*p1,*p2;
#define GC (p1==p2&&(p2=(p1=BF)+fread(BF,1,N,stdin),p1==p2)?0:*p1++)
I void Read(int&x){
char t=GC;
x=0;
bool tag=false;
while(t<48||t>57){
if(t=='-'){
tag=true;
}
t=GC;
}
while(t>47&&t<58){
x=(x<<3)+(x<<1)+(t^48);
t=GC;
}
if(tag==true){
x=-x;
}
}
int t[N],h[N],sz[N],dep[N],f[N],in[N],out[N],Top[N],ct;
vector<int>G[N];
I void PreDFS(int x){
dep[x]=dep[f[x]]+1;
sz[x]=1;
for(vector<int>::iterator T=G[x].begin();T!=G[x].end();T++){
PreDFS(*T);
if(sz[*T]>sz[h[x]]){
h[x]=*T;
}
}
}
I void ReDFS(int x,int t){
Top[x]=t;
ct++;
in[x]=ct;
if(h[x]!=0){
ReDFS(h[x],t);
for(vector<int>::iterator T=G[x].begin();T!=G[x].end();T++){
if(*T!=h[x]){
ReDFS(*T,*T);
}
}
}
}
L c1[N],c2[N];
I void Modify(int x,const int d){
L tem=(L)d*in[x];
int l=out[Top[x]];
for(R i=in[x];i<=l;i+=i&-i){
c1[i]+=d;
c2[i]+=tem;
}
}
I L GetSum(int x){
L res1=0,res2=0;
int l=in[Top[x]];
for(R i=in[x];i>=l;i&=i-1){
res1+=c1[i];
res2+=c2[i];
}
return res1*(in[x]+1)-res2;
}
int main(){
int n,q,opt,a,b,c;
Read(n);
Read(q);
for(R i=1;i<=n;i++){
Read(t[i]);
}
for(int i=2;i<=n;i++){
Read(f[i]);
G[f[i]].push_back(i);
}
PreDFS(1);
ReDFS(1,1);
for(R i=1;i<=n;i++){
if(in[i]>out[Top[i]]){
out[Top[i]]=in[i];
}
}
for(R i=1;i<=n;i++){
Modify(i,t[i]);
if(h[i]!=0){
Modify(h[i],-t[i]);
}
}
for(R i=0;i!=q;i++){
Read(opt);
Read(a);
Read(b);
if(opt==1){
Read(c);
while(Top[a]!=Top[b]){
if(dep[Top[a]]<dep[Top[b]]){
Swap(a,b);
}
Modify(Top[a],c);
if(h[a]!=0){
Modify(h[a],-c);
}
a=f[Top[a]];
}
if(dep[a]>dep[b]){
Swap(a,b);
}
Modify(a,c);
if(h[b]!=0){
Modify(h[b],-c);
}
}else{
L ans=0;
while(Top[a]!=Top[b]){
if(dep[Top[a]]<dep[Top[b]]){
Swap(a,b);
}
ans+=GetSum(a);
a=f[Top[a]];
}
if(dep[a]>dep[b]){
Swap(a,b);
}
if(Top[a]!=a){
ans-=GetSum(f[a]);
}
printf("%lld\n",ans+GetSum(b));
}
}
return 0;
}
总而言之,DFS和树链剖分都可以将树转化为区间,是非常好用的算法。