樹鏈剖分總結
轉載自zzq巨佬的樹鏈剖分:
http://blog.csdn.net/Love_mona/article/details/79344296
蒟蒻的垂死掙紮
(以洛谷上樹鏈剖分模板為題來介紹:[洛谷P3384] 【模板】樹鏈剖分)
聽說樹剖很簡單
樹剖大概算一種思想吧,通過一種巧妙的方式把一棵樹的節點有序地排在一個一維數組裏,使得其相關詢問可以通過線段樹等數據結構來維護。
那麽難點就在這了,這個巧妙的方法到底是什麽呢?
首先引入幾個概念:
- 重結點:子樹結點數目最多的結點;
- 輕節點:父親節點中除了重結點以外的結點;
- 重邊:父親結點和重結點連成的邊;
- 輕邊:父親節點和輕節點連成的邊;
- 重鏈:由多條重邊連接而成的路徑;
- 輕鏈:由多條輕邊連接而成的路徑;
其中,我們在剖分的過程中需要應用到的就是重鏈,重鏈的存在決定了其優秀的復雜度,依靠重鏈進行跨越就是關鍵所在。
首先是預處理部分
我們需要記錄 一個點的父親(fa) 深度(dep) 以它為根的子樹的大小(siz) 它的重兒子(son)
它所在重鏈的最高點(top) 重新排列後它在樹組中的位置(w) 以及重新排列後的數組(b)
第一步,我們用通常的dfs將第一行的四個元素處理,這個自己模擬沒問題,實在不會看後面整體代碼。
第二步,處理第二行的三個數組,同樣是一遍dfs,但是我們優先走重兒子,從而讓重鏈在重新排列後靠在一起,而對於top數組的記錄就帶一個參數往下傳就行了,詳見代碼。
void build(int k,int tp) { w[k]=++cnt,top[k]=tp,b[cnt]=a[k]; if(son[k]) build(son[k],top[k]); for(RG int i=first[k];i;i=s[i].nxt) if(s[i].en!=son[k]&&s[i].en!=fa[k]) build(s[i].en,s[i].en); }
接下來就是操作部分了
對於這一道模板題,我們需要解決的操作有如下幾種,本蒟蒻采用線段樹維護(前置任務 線段樹模板1),具體方法列在其後。
1.修改樹上兩點路徑上所有點的權值(區間加)2.求樹上兩點路徑上所有點的權值和(區間查詢)
這兩個操作就涉及到了樹剖最難的地方,因為兩點之間的路徑在線段樹中是沒有連在一起的,那麽要怎樣才能不重不漏地訪問到相應的節點,並且保證正確的復雜度呢,這時候重鏈就派上用場了。我們知道,一條重鏈在重新排列後的數組裏是靠在一起的,那麽我們每次就讓較深的節點往上跳,跳到它的top節點處,這樣每次跳躍做一個區間處理,直到這兩個點在同一條重鏈上,再進行最後的一個區間處理即可。(這個可以證明是log的時間復雜度)
我覺得寫兩個很像的函數太蠢了就強行把它們用一個轉換函數縮了一下,黑科技黑科技。
inline void Tr_Enter(int x,int y,int val,int op)//轉換 { if(w[x]>w[y]) swap(x,y); if(!op) update(1,w[x],w[y],val); else ans+=Query(1,w[x],w[y]),ans%=p; } inline void Tr_Chan(int x,int y,int w,int op) //跳躍(這個才是重點) { while(top[x]!=top[y]) { if(dep[top[x]]<dep[top[y]]) swap(x,y); Tr_Enter(top[x],x,w,op); x=fa[top[x]]; } Tr_Enter(x,y,w,op); }
3.修改以一個點為根的子樹的權值(區間加)4.查詢以一個點為根的子樹的權值和(區間查詢)
這兩個就簡單多了,因為是dfs構的樹,他們理所當然就在一起的啦。所以直接進行愉快的區間操作就好了,具體看後面整體代碼。
最終形態
#include<iostream> #include<cstdio> #include<cstdlib> #include<cmath> #include<cstring> #include<algorithm> #define RG register #define ll long long #define N 100020 #define ls (now<<1) #define rs ((now<<1)|1) using namespace std; inline ll rread() { ll x=0,o=1; char ch=getchar(); while((ch>‘9‘||ch<‘0‘)&&ch!=‘-‘) ch=getchar(); if(ch==‘-‘) o=-1,ch=getchar(); while(ch>=‘0‘&&ch<=‘9‘) x=x*10+ch-‘0‘,ch=getchar(); return x*o; } int n,m,r,p,cnt,t,ans,b[N],a[N],w[N],fa[N],son[N],first[N],top[N],dep[N],siz[N]; struct mona{ int nxt,en; } s[N<<2]; struct tree{ int l,r,w,siz,lazy; }tr[N<<2]; inline void Insert(int x,int y) { s[++t]=(mona) {first[x],y}; first[x]=t; } inline void Init() { n=rread(),m=rread(),r=rread(),p=rread(); for(RG int i=1;i<=n;i++) a[i]=rread(),siz[i]=1; for(RG int i=1;i<n;i++) { int x=rread(),y=rread(); Insert(x,y),Insert(y,x); } } void dfs(int fat,int k,int deep) //預處理fa dep siz son { fa[k]=fat,dep[k]=deep; int num=0; for(RG int i=first[k];i;i=s[i].nxt) { int en=s[i].en; if(fat==en) continue ; dfs(k,en,deep+1); siz[k]+=siz[en]; if(siz[en]>num) num=siz[en],son[k]=en; } } void build(int k,int tp) { w[k]=++cnt,top[k]=tp,b[cnt]=a[k]; if(son[k]) build(son[k],top[k]); for(RG int i=first[k];i;i=s[i].nxt) if(s[i].en!=son[k]&&s[i].en!=fa[k]) build(s[i].en,s[i].en); } //-----------------------------------------------以上為DFS預處理部分 void Build(int now,int l,int r) { tr[now]=(tree) {l,r},tr[now].siz=r-l+1; if(l==r) {tr[now].w=b[l]; return ;} int mid=(l+r)/2; Build(ls,l,mid),Build(rs,mid+1,r); tr[now].w=(tr[ls].w+tr[rs].w+p)%p; } inline void pushdown(int now) { tr[ls].w+=tr[now].lazy*tr[ls].siz,tr[rs].w+=tr[now].lazy*tr[rs].siz; tr[ls].lazy+=tr[now].lazy ,tr[rs].lazy+=tr[now].lazy; tr[ls].w%=p,tr[ls].lazy%=p,tr[rs].w%=p,tr[rs].lazy%=p; tr[now].lazy=0; } void update(int now,int l,int r,int val) { if(tr[now].l>=l&&tr[now].r<=r) { tr[now].w+=val*tr[now].siz; tr[now].lazy+=val; return ; } pushdown(now); int mid=(tr[now].l+tr[now].r)/2; if(l<=mid) update(ls,l,r,val); if(r>mid) update(rs,l,r,val); tr[now].w=(tr[ls].w+tr[rs].w+p)%p; } int Query(int now,int l,int r) { int ans=0; if(tr[now].l>=l&&tr[now].r<=r) return tr[now].w; pushdown(now); int mid=(tr[now].l+tr[now].r)/2; if(l<=mid) ans+=Query(ls,l,r); if(r>mid) ans+=Query(rs,l,r); tr[now].w=(tr[ls].w+tr[rs].w+p)%p,ans%=p; return ans; } //-----------------------------------------------以上為線段樹部分 inline void Tr_Enter(int x,int y,int val,int op) { if(w[x]>w[y]) swap(x,y); if(!op) update(1,w[x],w[y],val); else ans+=Query(1,w[x],w[y]),ans%=p; } inline void Tr_Chan(int x,int y,int w,int op) { while(top[x]!=top[y]) { if(dep[top[x]]<dep[top[y]]) swap(x,y); Tr_Enter(top[x],x,w,op); x=fa[top[x]]; } Tr_Enter(x,y,w,op); } inline void Ans() { for(RG int i=1;i<=m;i++) { int op=rread();ans=0; if(op==1) { int x=rread(),y=rread(),z=rread(); Tr_Chan(x,y,z,0); } if(op==2) { int x=rread(),y=rread(); Tr_Chan(x,y,0,1); } if(op==3) { int x=rread(),z=rread(); update(1,w[x],w[x]+siz[x]-1,z); } if(op==4) { int x=rread(); ans=Query(1,w[x],w[x]+siz[x]-1); } if(op==2||op==4) printf("%d\n",ans); } } //---------------------------------------以上為答案處理 int main() { Init(); dfs(0,r,0); build(r,r); Build(1,1,n); Ans(); }
樹鏈剖分總結