基础 DFS序、树链剖分

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 inuinvoutu。因此任意一点 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(depvdepu+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(depu1)vdv。因此树状数组维护 d d d 的线性和和 d ⋅ d e p d·dep ddep 的和。
时间复杂度 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 ddep 的和。
时间复杂度 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和树链剖分都可以将树转化为区间,是非常好用的算法。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值