树链剖分 完美的想法
樹鏈剖分
不知是誰想出的想法,太完美了,首先我大致講一下樹剖的想法。
將樹分成重鏈和輕鏈,使每條重鏈越長越好,每次可以用數據結構將重鏈上的所有節點求出或修改,達到優化的效果,下面我講的是用線段樹維護一棵樹。
當然不止是線段樹可以維護,樹狀數組和Splay也可以。
下面看一道題:
洛谷3384
題目描述
如題,已知一棵包含N個結點的樹(連通且無環),每個節點上包含一個數值,需要支持以下操作:
操作1: 格式: 1 x y z 表示將樹從x到y結點最短路徑上所有節點的值都加上z
操作2: 格式: 2 x y 表示求樹從x到y結點最短路徑上所有節點的值之和
操作3: 格式: 3 x z 表示將以x為根節點的子樹內所有節點值都加上z
操作4: 格式: 4 x 表示求以x為根節點的子樹內所有節點值之和
輸入格式:
第一行包含4個正整數N、M、R、P,分別表示樹的結點個數、操作個數、根節點序號和取模數(即所有的輸出結果均對此取模)。
接下來一行包含N個非負整數,分別依次表示各個節點上初始的數值。
接下來N-1行每行包含兩個整數x、y,表示點x和點y之間連有一條邊(保證無環且連通)
接下來M行每行包含若干個正整數,每行表示一個操作,格式如下:
操作1: 1 x y z
操作2: 2 x y
操作3: 3 x z
操作4: 4 x
輸出格式:
輸出包含若干行,分別依次表示每個操作2或操作4所得的結果(對P取模)
輸入樣例#1:
5 5 2 24 7 3 7 8 0 1 2 1 5 3 1 4 1 3 4 2 3 2 2 4 5 1 5 1 3 2 1 3輸出樣例#1:
2 21說明
時空限制:1s,128M
數據規模:
對于30%的數據: N \leq 10, M \leq 10N≤10,M≤10
對于70%的數據: N \leq {10}^3, M \leq {10}^3N≤103,M≤103
對于100%的數據: N \leq {10}^5, M \leq {10}^5N≤105,M≤105
( 其實,純隨機生成的樹LCA+暴力是能過的,可是,你覺得可能是純隨機的么233 )
樣例說明:
樹的結構如下:
各個操作如下:
故輸出應依次為2、21(重要的事情說三遍:記得取模)
樹剖可以解決樹上路徑修改和子樹修改,復雜度應該是log2Nlog2N
黑色邊表示重鏈,當然重鏈是一條鏈,每個節點有且僅有一條重鏈與它的子節點相連。
先介紹變量:
int n,m,Rot,MOD,W[MAXN],a[MAXN];//Rot是根節點,MOD是要%的數 int Tre[MAXN<<2],Add[MAXN<<2];//線段樹 struct Edge{//這是存邊的int tot,lnk[MAXN],son[2*MAXN],nxt[2*MAXN];void Add(int x,int y){son[++tot]=y;nxt[tot]=lnk[x];lnk[x]=tot;} }E; int cnt,ID[MAXN];//存DFS序 int Dep[MAXN];//深度 int Son[MAXN];//重兒子,也就是重鏈連接的子節點 int Siz[MAXN];//子樹大小 int Fa[MAXN];//父節點 int Top[MAXN];//這條重鏈頂端的節點,可以優化查找預處理1:
標記重兒子節點,得到深度和子樹大小。
void First(int x,int f,int D){Dep[x]=D,Siz[x]=1,Fa[x]=f;for(int j=E.lnk[x],NowSize=0;j;j=E.nxt[j])if(E.son[j]!=f){First(E.son[j],x,D+1);Siz[x]+=Siz[E.son[j]];if(Siz[E.son[j]]>NowSize) NowSize=Siz[E.son[j]],Son[x]=E.son[j];//我們要使重鏈越長越好,那么就選子樹最大的連接。} }預處理2:
求出DFS序,用線段樹維護。
我們以先根,然后重兒子,然后輕兒子的順序DFS序,這樣可以保證一條重鏈是連續的節點,方便更新重鏈。
void Second(int x,int top){ID[x]=++cnt,W[cnt]=a[x],Top[x]=top;if(!Son[x]) return;Second(Son[x],top);for(int j=E.lnk[x];j;j=E.nxt[j])if(!ID[E.son[j]]) Second(E.son[j],E.son[j]); }以下是區間修改線段樹
void PushDown(int x,int Ln,int Rn){if(!Add[x]) return;Tre[x<<1]=(Tre[x<<1]+Ln*Add[x]%MOD)%MOD,Tre[x<<1|1]=(Tre[x<<1|1]+Rn*Add[x]%MOD)%MOD;Add[x<<1]=(Add[x<<1]+Add[x])%MOD,Add[x<<1|1]=(Add[x<<1|1]+Add[x])%MOD,Add[x]=0; } void Build(int x,int l,int r){if(l==r){Tre[x]=W[l]%MOD;return;}//W是DFS序過后的a數組 int mid=(r+l)>>1;Build(x<<1,l,mid);Build(x<<1|1,mid+1,r);Tre[x]=(Tre[x<<1]+Tre[x<<1|1])%MOD; } void Updata(int x,int l,int r,int L,int R,int p){if(L<=l&&r<=R){Tre[x]=(Tre[x]+(r-l+1)*p)%MOD;Add[x]=(Add[x]+p)%MOD;return;}int mid=(r+l)>>1;PushDown(x,(mid-l+1)%MOD,(r-mid)%MOD);if(L<=mid) Updata(x<<1,l,mid,L,R,p);if(R>mid) Updata(x<<1|1,mid+1,r,L,R,p);Tre[x]=(Tre[x<<1]+Tre[x<<1|1])%MOD; } int Query(int x,int l,int r,int L,int R){if(L<=l&&r<=R) return Tre[x]%MOD;int mid=(r+l)>>1;PushDown(x,(mid-l+1)%MOD,(r-mid)%MOD);int Ans=0;if(L<=mid) Ans+=Query(x<<1,l,mid,L,R);if(R>mid) Ans+=Query(x<<1|1,mid+1,r,L,R);return Ans%MOD; }將x這棵子樹全部加上p
我們可以知道這棵子樹所在的區間是ID[x]~ID[x]+Siz[x]-1,很好理解,因為這是DFS序后的。
Updata(1,1,n,ID[x],ID[x]+Siz[x]-1,z%MOD);求x這棵子樹
Query(1,1,n,ID[x],ID[x]+Siz[x]-1)將這棵子樹x到y的路徑上都加上p
用類似LCA的想法,每次將重鏈上的所有點都加上p。
void Insert(int x,int y,int p){while(Top[x]!=Top[y]){if(Dep[Top[x]]>Dep[Top[y]]) swap(x,y);Updata(1,1,n,ID[Top[y]],ID[y],p);y=Fa[Top[y]];}if(Dep[x]>Dep[y]) swap(x,y);Updata(1,1,n,ID[x],ID[y],p); }求這棵子樹x到y的路徑的加和
還是一樣
int AskSum(int x,int y){int ret=0;while(Top[x]!=Top[y]){if(Dep[Top[x]]>Dep[Top[y]]) swap(x,y);ret=(ret+Query(1,1,n,ID[Top[y]],ID[y]))%MOD;y=Fa[Top[y]];}if(Dep[x]>Dep[y]) swap(x,y);ret=(ret+Query(1,1,n,ID[x],ID[y]))%MOD;return ret; }下面貼上完整代碼
#include<cstdio> #include<cctype> #include<iostream> #include<algorithm> #define MAXN 100005 using namespace std; int n,m,Rot,MOD,W[MAXN],a[MAXN],Tre[MAXN<<2],Add[MAXN<<2]; struct Edge{int tot,lnk[MAXN],son[2*MAXN],nxt[2*MAXN];void Add(int x,int y){son[++tot]=y;nxt[tot]=lnk[x];lnk[x]=tot;} }E; int cnt,ID[MAXN],Dep[MAXN],Son[MAXN],Siz[MAXN],Fa[MAXN],Top[MAXN]; int read(){int ret=0;char ch=getchar();bool f=1;for(;!isdigit(ch);ch=getchar()) f^=!(ch^'-');for(; isdigit(ch);ch=getchar()) ret=(ret<<3)+(ret<<1)+ch-48;return f?ret:-ret; } void First(int x,int f,int D){Dep[x]=D,Siz[x]=1,Fa[x]=f;for(int j=E.lnk[x],NowSize=0;j;j=E.nxt[j])if(E.son[j]!=f){First(E.son[j],x,D+1);Siz[x]+=Siz[E.son[j]];if(Siz[E.son[j]]>NowSize) NowSize=Siz[E.son[j]],Son[x]=E.son[j];} } void Second(int x,int top){ID[x]=++cnt,W[cnt]=a[x],Top[x]=top;if(!Son[x]) return;Second(Son[x],top);for(int j=E.lnk[x];j;j=E.nxt[j])if(!ID[E.son[j]]) Second(E.son[j],E.son[j]); } void PushDown(int x,int Ln,int Rn){if(!Add[x]) return;Tre[x<<1]=(Tre[x<<1]+Ln*Add[x]%MOD)%MOD,Tre[x<<1|1]=(Tre[x<<1|1]+Rn*Add[x]%MOD)%MOD;Add[x<<1]=(Add[x<<1]+Add[x])%MOD,Add[x<<1|1]=(Add[x<<1|1]+Add[x])%MOD,Add[x]=0; } void Build(int x,int l,int r){if(l==r){Tre[x]=W[l]%MOD;return;} int mid=(r+l)>>1;Build(x<<1,l,mid);Build(x<<1|1,mid+1,r);Tre[x]=(Tre[x<<1]+Tre[x<<1|1])%MOD; } void Updata(int x,int l,int r,int L,int R,int p){if(L<=l&&r<=R){Tre[x]=(Tre[x]+(r-l+1)*p)%MOD;Add[x]=(Add[x]+p)%MOD;return;}int mid=(r+l)>>1;PushDown(x,(mid-l+1)%MOD,(r-mid)%MOD);if(L<=mid) Updata(x<<1,l,mid,L,R,p);if(R>mid) Updata(x<<1|1,mid+1,r,L,R,p);Tre[x]=(Tre[x<<1]+Tre[x<<1|1])%MOD; } int Query(int x,int l,int r,int L,int R){if(L<=l&&r<=R) return Tre[x]%MOD;int mid=(r+l)>>1;PushDown(x,(mid-l+1)%MOD,(r-mid)%MOD);int Ans=0;if(L<=mid) Ans+=Query(x<<1,l,mid,L,R);if(R>mid) Ans+=Query(x<<1|1,mid+1,r,L,R);return Ans%MOD; } void Insert(int x,int y,int p){while(Top[x]!=Top[y]){if(Dep[Top[x]]>Dep[Top[y]]) swap(x,y);Updata(1,1,n,ID[Top[y]],ID[y],p);y=Fa[Top[y]];}if(Dep[x]>Dep[y]) swap(x,y);Updata(1,1,n,ID[x],ID[y],p); } int AskSum(int x,int y){int ret=0;while(Top[x]!=Top[y]){if(Dep[Top[x]]>Dep[Top[y]]) swap(x,y);ret=(ret+Query(1,1,n,ID[Top[y]],ID[y]))%MOD;y=Fa[Top[y]];}if(Dep[x]>Dep[y]) swap(x,y);ret=(ret+Query(1,1,n,ID[x],ID[y]))%MOD;return ret; } int main(){#ifndef ONLINE_JUDGEfreopen("prob.in","r",stdin);freopen("prob.out","w",stdout);#endifn=read();m=read();Rot=read();MOD=read();for(int i=1;i<=n;i++) a[i]=read(),a[i]%=MOD;for(int i=1;i<n;i++){int x=read(),y=read();E.Add(x,y),E.Add(y,x);}First(Rot,0,1);Second(Rot,Rot);Build(1,1,n);for(int i=1;i<=m;i++){int opt=read();if(opt==1){int x=read(),y=read(),z=read();Insert(x,y,z%MOD);}elseif(opt==2){int x=read(),y=read();printf("%d\n",AskSum(x,y));}elseif(opt==3){int x=read(),z=read();Updata(1,1,n,ID[x],ID[x]+Siz[x]-1,z%MOD);}else{int x=read();printf("%d\n",Query(1,1,n,ID[x],ID[x]+Siz[x]-1));}}return 0; }轉載于:https://www.cnblogs.com/XSamsara/p/9190122.html
總結
以上是生活随笔為你收集整理的树链剖分 完美的想法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 洛谷3195(HNOI2008)玩具装箱
- 下一篇: OpenGL教程一