對一棵樹進行剖分,將其分紅幾條鏈,將樹形變爲線性,減小處理的難度
須要處理的問題有node
void dfs1(int x,int f,int deep)//x當前節點,f父親,deep深度 { dep[x]=deep;//標記深度 fa[x]=f;//標記每一個點的父親 siz[x]=1;//標記每一個非葉子節點的子樹的大小 int maxson=-1;//記錄重兒子的兒子數量 for(int i=head[x];i;i=e[i].last) { int y=e[i].to; if(y==f) continue;//若是是父親那麼就繼續去找下一個 dfs1(y,x,deep+1); siz[x]+=siz[y];//加上子樹的節點數量 if(siz[y]>maxson) { son[x]=y; maxson=siz[y];//若是該子節點更大,那麼就標記他的每一個非葉子節點的 //重兒子的編號 } } }
void dfs2(int x,int topf)//x爲當前的節點,topf爲當前鏈上最頂端的節點 { id[x]=++cnt;//標記每一個點的新編號 wt[cnt]=w[x];//把每一個點的初始值都賦到新的編號上來 top[x]=topf;//標記這個點所在的鏈的頂端 if(!son[x]) return;//若是沒有重兒子(兒子),那麼就返回 dfs2(son[x],topf);//先處理重兒子,在處理輕兒子,遞歸處理 for(int i=head[x];i;i=e[i].last) { int y=e[i].to; if(y==fa[x]||y==son[x])continue; //若是遍歷到父親結點或者是重兒子,那麼就繼續搜索 dfs2(y,y); //每個輕兒子都有一條從本身開始的鏈 } }
由於順序是按照先重兒子,再輕兒子來處理的,因此每一條重鏈的新編號是連續的
由於是用的\(DFS\)因此每個子樹的新編號也是連續的ios
在這個時候咱們注意到,咱們所要處理的全部的區間都是連續的編號(新編號),那麼咱們能夠用線段樹處理連續編號區間和,每次查詢時間複雜度爲\(O(log^2n)\)數組
int qRange(int x,int y) { int ans=0; while(top[x]!=top[y]) { if(dep[top[x]]<dep[top[y]])//把x改在所在鏈更深的點 swap(x,y); res=0; query(1,1,n,id[top[x]],id[x]);//ans加上x點到所在鏈頂端的這一區間的點權和 ans+=res; ans%=mod; x=fa[top[x]]; } if(dep[x]>dep[y])swap(x,y); res=0; query(1,1,n,id[x],id[y]); ans+=res; return ans%mod; }
記錄每個非葉子節點的子樹的大小,而且每個子樹的新編號都是連續的,因而就直接線段樹區間查詢便可時間複雜度爲\(O(log n)\)ui
int qson(int x) { res=0; query(1,1,n,id[x],id[x]+siz[x]-1);//子樹的右端點爲id[x]+siz[x]-1,能夠手推一下 return res; }
void updson(int x,int k) { update(1,1,n,id[x],id[x]+siz[x]-1,k); } void updRange(int x,int y,int k)//區間修改 { k%=mod; while(top[x]!=top[y]) { if(dep[top[x]]<dep[top[y]])//讓x的深度更深 swap(x,y); update(1,1,n,id[top[x]],id[x],k); x=fa[top[x]]; } if(dep[x]>dep[y]) swap(x,y); update(1,1,n,id[x],id[y],k); }
#include<iostream> #include<cstdio> #include<cstring> #include<queue> #include<stack> #include<map> #include<cmath> #include<algorithm> using namespace std; #define mid ((l+r)>>1) #define lson rt<<1,l,mid #define rson rt<<1|1,mid+1,r #define len (r-l+1) const int N=2e5+10; struct node{ int last; int to; }e[N]; int head[N]; int n,m,r,mod; int e_cnt,w[N],wt[N]; int a[N<<2],laz[N<<2]; //線段樹數組,懶惰標記 int son[N],id[N],fa[N],cnt,dep[N],siz[N],top[N]; //重兒子編號,新編號,父親結點,dfs序,深度,子樹的大小,當前鏈的頂端結點 int res=0; void add(int from,int to) { e[++e_cnt].last=head[from]; e[e_cnt].to=to; head[from]=e_cnt; } //------------------------------------------------線段樹 void pushdown(int rt,int lenn) { laz[rt<<1]+=laz[rt]; laz[rt<<1|1]+=laz[rt]; a[rt<<1]+=laz[rt]*(lenn-(lenn>>1)); a[rt<<1|1]+=laz[rt]*(lenn>>1); a[rt<<1]%=mod; a[rt<<1|1]%=mod; laz[rt]=0; } void build(int rt,int l,int r) { if(l==r) { a[rt]=wt[l];//賦值點權值 if(a[rt]>mod) a[rt]%=mod; return; } build(lson); build(rson); a[rt]=(a[rt<<1]+a[rt<<1|1])%mod; } void query(int rt,int l,int r,int L,int R) { if(L<=l&&r<=R) { res+=a[rt]; res%=mod; return; } else { if(laz[rt]) pushdown(rt,len); if(L<=mid) query(lson,L,R); if(R>mid) query(rson,L,R); } } void update(int rt,int l,int r,int L,int R,int k) //當前節點,當前區間的左,右,要修改的區間左,右,修改值 { if(L<=l&&r<=R) { laz[rt]+=k; a[rt]+=k*len; } else { if(laz[rt]) pushdown(rt,len); if(L<=mid) update(lson,L,R,k); if(R>mid) update(rson,L,R,k); a[rt]=(a[rt<<1]+a[rt<<1|1])%mod; } } //------------------------------------------------樹鏈剖分 int qRange(int x,int y) { int ans=0; while(top[x]!=top[y]) { if(dep[top[x]]<dep[top[y]])//把x改在所在鏈更深的點 swap(x,y); res=0; query(1,1,n,id[top[x]],id[x]);//ans加上x點到所在鏈頂端的這一區間的點權和 ans+=res; ans%=mod; x=fa[top[x]]; } if(dep[x]>dep[y])swap(x,y); res=0; query(1,1,n,id[x],id[y]); ans+=res; return ans%mod; } void updRange(int x,int y,int k)//區間修改 { k%=mod; while(top[x]!=top[y]) { if(dep[top[x]]<dep[top[y]])//讓x的深度更深 swap(x,y); update(1,1,n,id[top[x]],id[x],k); x=fa[top[x]]; } if(dep[x]>dep[y]) swap(x,y); update(1,1,n,id[x],id[y],k); } int qson(int x) { res=0; query(1,1,n,id[x],id[x]+siz[x]-1); return res; } void updson(int x,int k) { update(1,1,n,id[x],id[x]+siz[x]-1,k); } void dfs1(int x,int f,int deep)//x當前節點,f父親,deep深度 { dep[x]=deep;//標記深度 fa[x]=f;//標記每一個點的父親 siz[x]=1;//標記每一個非葉子節點的子樹的大小 int maxson=-1;//記錄重兒子的兒子數量 for(int i=head[x];i;i=e[i].last) { int y=e[i].to; if(y==f) continue;//若是是父親那麼就繼續去找下一個 dfs1(y,x,deep+1); siz[x]+=siz[y];//加上子樹的節點數量 if(siz[y]>maxson) { son[x]=y; maxson=siz[y];//若是該子節點更大,那麼就標記他的每一個非葉子節點的 //重兒子的編號 } } } void dfs2(int x,int topf)//x爲當前的節點,topf爲當前鏈上最頂端的節點 { id[x]=++cnt;//標記每一個點的新編號 wt[cnt]=w[x];//把每一個點的初始值都賦到新的編號上來 top[x]=topf;//標記這個點所在的鏈的頂端 if(!son[x]) return;//若是沒有重兒子(兒子),那麼就返回 dfs2(son[x],topf);//先處理重兒子,在處理輕兒子,遞歸處理 for(int i=head[x];i;i=e[i].last) { int y=e[i].to; if(y==fa[x]||y==son[x])continue; //若是遍歷到父親結點或者是重兒子,那麼就繼續搜索 dfs2(y,y); //每個輕兒子都有一條從本身開始的鏈 } } int main() { cin>>n>>m>>r>>mod; for(int i=1;i<=n;i++) cin>>w[i];//節點的初始權值 for(int i=1;i<n;i++) { int x,y; cin>>x>>y; add(x,y); add(y,x); } dfs1(r,0,1); dfs2(r,r); build(1,1,n); while(m--) { int k,x,y,z; cin>>k; if(k==1) { cin>>x>>y>>z; updRange(x,y,z); } else if(k==2) { cin>>x>>y; cout<<qRange(x,y)<<endl; } else if(k==3) { cin>>x>>y; updson(x,y); } else { cin>>x; cout<<qson(x)<<endl; } } return 0; }