樹鏈剖分,是一種能夠把一棵有根樹劃分紅許多條鏈,從而簡單地實現樹上修改與查詢操做的 算法/數據結構(我也不知道屬於哪一個QwQ)。
固然這裏的樹鏈剖分是指重鏈剖分。算法
先放模板:P3384 【模板】輕重鏈剖分
(嚶嚶嚶她藍了)數據結構
學習重鏈剖分,你首先要知道如下名詞:學習
重兒子: 對於一個非葉子節點u,有許多子節點。而其中有一個子節點v,以他爲根的子樹的大小比其餘子節點都大,那麼v就是u的重兒子。
輕兒子: 不是重兒子就是輕兒子。
重鏈: 一條除了頂部是輕兒子,其餘都是重兒子的路徑。ui
光是文字好像不容易搞懂,那麼來看看這張圖:
重兒子和重鏈已經用紅色標出來了。spa
接下來就是樹剖的實現啦!code
樹剖實際上就是兩遍預處理。第一遍咱們要求出每一個點的父節點
、深度
、子樹大小
、重兒子
,分別記爲fa
,depth
,size
,son
。
其餘三個都是信手拈來,而這個重兒子嘛……就是求子樹最大的那個點啦!因而代碼就很天然得寫出來了:blog
void dfs1(int now,int F) { int k=0;//k用來記錄當前找到的子樹最大的點的子樹大小 fa[now]=F,size[now]=1; depth[now]=depth[F]+1; //你們都熟悉的求父節點和深度 for(int i=g.hd[now];i;i=g.nxt[i]) if(g.to[i]!=F)//首先不能是父節點 { dfs1(g.to[i],now);//遞歸 size[now]+=size[g.to[i]];//依然很熟悉的求子樹大小 if(size[g.to[i]]>k)//這棵子樹的大小比以前的更大呢 { k=size[g.to[i]]; son[now]=g.to[i]; //那麼就更新 } } return ; }
第二遍咱們要求每一個點所在的重鏈的頂端
和第幾個被遍歷
,分別計爲top
和id
。
那麼這個怎麼求呢?很簡單,咱們不用記錄父節點了(上面已經求出來了),而是記錄 這個點所在的重鏈的頂端 。這樣就能夠解決top
了:遞歸
void dfs2(int now,int F)//這裏的F是now所在的重鏈的頂端 { top[now]=F;//記錄top id[now]=++cnt;//記錄id wt[id[now]]=a[now];//這裏等下再解釋 if(son[now]==0) return ;//重兒子是0,就是沒有重兒子,那麼就是葉子節點,結束。 dfs2(son[now],F);//要先遞歸重兒子哦 for(int i=g.hd[now];i;i=g.nxt[i]) if(g.to[i]!=fa[now]&&g.to[i]!=son[now])//是輕兒子 dfs2(g.to[i],g.to[i]);//遞歸 return ; }
而後你就學會樹剖了(逃
而後你就要解決修改和查詢了。
在這以前,先觀察一下id
,能夠發如今一條重鏈上,id
是連續的。在一棵子樹裏也是。
而後咱們就須要這個性質來解決修改和查詢了。get
先看第一個操做:io
將樹從x到y結點最短路徑上全部節點的值都加上z
回想一下倍增LCA怎麼搞?深度大的往上跳,直到父親相同。這裏也是同樣。因爲一條重鏈上id
連續,因此每條重鏈求和只要讓id[top[u]]
到id[u]
都加上k
就OK了。而這個操做可讓線段樹來完成。這也是爲何我以前在第二遍\(dfs\)的時候要wt[id[now]]=a[now];
。線段樹建樹的時候就是把wt的值賦上去的。
以後,再讓u
跳到fa[top[u]]
便可:
void add_path(int x,int y,int k) { while(top[x]!=top[y])//不在同一條重鏈上 { if(depth[top[x]]<depth[top[y]]) swap(x,y);//讓x是深度大的那個 tr.change(id[top[x]],id[x],1,k);//區間修改 x=fa[top[x]];//往上跳 } if(depth[x]>depth[y]) swap(x,y);//這裏在同一條重鏈上,要讓x作深度小的那個了 tr.change(id[x],id[y],1,k);//修改他們中間的那段 return ; }
操做2也同樣,只不過把修改改爲了查詢:
int query_path(int x,int y) { int res=0; while(top[x]!=top[y]) { if(depth[top[x]]<depth[top[y]]) swap(x,y); res=(res+tr.ask(id[top[x]],id[x],1))%Mod;//記得取模 x=fa[top[x]]; } if(depth[x]>depth[y]) swap(x,y); res=(res+tr.ask(id[x],id[y],1))%Mod;//這裏也是 return res; }
而後是操做3:。
將以x爲根節點的子樹內全部節點值都加上z。
因爲子樹內的id
連續,因此最小的那個是id[u]
,共有size[u]
個,因此修改的區間就是id[u]
到id[u]+size[u]-1
:
void add_son(int x,int k) { tr.change(id[x],id[x]+size[x]-1,1,k); return ; }
操做4也同樣:
int query_son(int x) { return tr.ask(id[x],id[x]+size[x]-1,1); }
好了,這題就結束了!總複雜度\(O(nlog^2n)\)。
總體代碼長這個亞子:
#include<cstdio> #define MAXN 100005 #define int long long using namespace std; int n,m,Root,Mod,cnt; int a[MAXN],wt[MAXN]; int fa[MAXN],size[MAXN],depth[MAXN]; int son[MAXN],top[MAXN],id[MAXN]; void swap(int &x,int &y) { int t=x; x=y; y=t; return ; } struct graph { int tot,hd[MAXN]; int nxt[MAXN*2],to[MAXN*2]; void add(int u,int v) { tot++; nxt[tot]=hd[u]; hd[u]=tot; to[tot]=v; return ; } }g; struct Tree { int w[MAXN*4],l[MAXN*4],r[MAXN*4]; int f[MAXN*4]; void build(int ll,int rr,int k) { l[k]=ll,r[k]=rr; if(ll==rr) { w[k]=wt[ll]%Mod; return ; } int mid=(ll+rr)/2; build(ll,mid,k*2); build(mid+1,rr,k*2+1); w[k]=(w[k*2]+w[k*2+1])%Mod; return ; } void down(int k) { f[k*2]=(f[k*2]+f[k])%Mod; f[k*2+1]=(f[k*2+1]+f[k])%Mod; w[k*2]=(w[k*2]+f[k]*(r[k*2]-l[k*2]+1)%Mod)%Mod; w[k*2+1]=(w[k*2+1]+f[k]*(r[k*2+1]-l[k*2+1]+1)%Mod)%Mod; f[k]=0; return ; } void change(int ll,int rr,int k,int x) { if(l[k]>=ll&&r[k]<=rr) { w[k]=(w[k]+x*(r[k]-l[k]+1)%Mod)%Mod; f[k]=(f[k]+x)%Mod; return ; } if(f[k]) down(k); int mid=(l[k]+r[k])/2; if(ll<=mid) change(ll,rr,k*2,x); if(rr>mid) change(ll,rr,k*2+1,x); w[k]=w[k*2]+w[k*2+1]; return ; } int ask(int ll,int rr,int k) { if(l[k]>=ll&&r[k]<=rr) return w[k]%Mod; if(f[k]) down(k); int res=0,mid=(l[k]+r[k])/2; if(ll<=mid) res=(res+ask(ll,rr,k*2))%Mod; if(rr>mid) res=(res+ask(ll,rr,k*2+1))%Mod; return res; } }tr; void dfs1(int now,int F) { int k=0; fa[now]=F,size[now]=1; depth[now]=depth[F]+1; for(int i=g.hd[now];i;i=g.nxt[i]) if(g.to[i]!=F) { dfs1(g.to[i],now); size[now]+=size[g.to[i]]; if(size[g.to[i]]>k) { k=size[g.to[i]]; son[now]=g.to[i]; } } return ; } void dfs2(int now,int F) { top[now]=F; id[now]=++cnt; wt[id[now]]=a[now]; if(son[now]==0) return ; dfs2(son[now],F); for(int i=g.hd[now];i;i=g.nxt[i]) if(g.to[i]!=fa[now]&&g.to[i]!=son[now]) dfs2(g.to[i],g.to[i]); return ; } void add_path(int x,int y,int k) { while(top[x]!=top[y]) { if(depth[top[x]]<depth[top[y]]) swap(x,y); tr.change(id[top[x]],id[x],1,k); x=fa[top[x]]; } if(depth[x]>depth[y]) swap(x,y); tr.change(id[x],id[y],1,k); return ; } int query_path(int x,int y) { int res=0; while(top[x]!=top[y]) { if(depth[top[x]]<depth[top[y]]) swap(x,y); res=(res+tr.ask(id[top[x]],id[x],1))%Mod; x=fa[top[x]]; } if(depth[x]>depth[y]) swap(x,y); res=(res+tr.ask(id[x],id[y],1))%Mod; return res; } void add_son(int x,int k) { tr.change(id[x],id[x]+size[x]-1,1,k); return ; } int query_son(int x) { return tr.ask(id[x],id[x]+size[x]-1,1); } signed main() { scanf("%lld%lld%lld%lld",&n,&m,&Root,&Mod); for(int i=1;i<=n;i++) scanf("%lld",&a[i]); for(int i=1;i<n;i++) { int u,v; scanf("%lld%lld",&u,&v); g.add(u,v); g.add(v,u); } dfs1(Root,0); dfs2(Root,Root); tr.build(1,n,1); for(int i=1;i<=m;i++) { int opt; scanf("%lld",&opt); if(opt==1) { int x,y,k; scanf("%lld%lld%lld",&x,&y,&k); add_path(x,y,k); } else if(opt==2) { int x,y; scanf("%lld%lld",&x,&y); printf("%lld\n",query_path(x,y)); } else if(opt==3) { int x,k; scanf("%lld%lld",&x,&k); add_son(x,k); } else { int x; scanf("%lld",&x); printf("%lld\n",query_son(x)); } } return 0; }
固然,樹剖也能用來求\(LCA\),怎麼求的話……看看上面的操做1操做2就能明白吧。