轉載請註明出處,部份內容引自banananana大神的博客html
別說你不知道什麼是樹╮(─▽─)╭(幫你百度一下)node
前置知識: dfs序 線段樹ios
先來回顧兩個問題:
1,將樹從x到y結點最短路徑上全部節點的值都加上z數組
這也是個模板題了吧數據結構
咱們很容易想到,樹上差分能夠以O(n+m)的優秀複雜度解決這個問題ide
2,求樹從x到y結點最短路徑上全部節點的值之和優化
lca大水題,咱們又很容易地想到,dfs O(n)預處理每一個節點的dis(即到根節點的最短路徑長度)ui
而後對於每一個詢問,求出x,y兩點的lca,利用lca的性質distance ( x , y ) = dis ( x ) + dis ( y ) - 2 * dis ( lca )求出結果spa
時間複雜度O(mlogn+n)3d
如今來思考一個bug:
若是剛纔的兩個問題結合起來,成爲一道題的兩種操做呢?
剛纔的方法顯然就不夠優秀了(每次詢問以前要跑dfs更新dis)
樹鏈剖分華麗登場
樹剖是經過輕重邊剖分將樹分割成多條鏈,而後利用數據結構來維護這些鏈(本質上是一種優化暴力)
首先明確概念:
重兒子:父親節點的全部兒子中子樹結點數目最多(size最大)的結點;
輕兒子:父親節點中除了重兒子之外的兒子;
重邊:父親結點和重兒子連成的邊;
輕邊:父親節點和輕兒子連成的邊;
重鏈:由多條重邊鏈接而成的路徑;
輕鏈:由多條輕邊鏈接而成的路徑;
好比上面這幅圖中,用黑線鏈接的結點都是重結點,其他均是輕結點,
2-11就是重鏈,2-5就是輕鏈,用紅點標記的就是該結點所在重鏈的起點,也就是下文提到的top結點,
還有每條邊的值實際上是進行dfs時的執行序號。
變量聲明:
const int maxn=1e5+10; struct edge{ int next,to; }e[2*maxn]; struct Node{ int sum,lazy,l,r,ls,rs; }node[2*maxn]; int rt,n,m,r,a[maxn],cnt,head[maxn],f[maxn],d[maxn],size[maxn],son[maxn],rk[maxn],top[maxn],id[maxn];
名稱 | 解釋 |
f[u] | 保存結點u的父親節點 |
d[u] | 保存結點u的深度值 |
size[u] | 保存以u爲根的子樹節點個數 |
son[u] | 保存重兒子 |
rk[u] | 保存當前dfs標號在樹中所對應的節點 |
top[u] | 保存當前節點所在鏈的頂端節點 |
id[u] | 保存樹中每一個節點剖分之後的新編號(DFS的執行順序) |
咱們要作的就是(樹鏈剖分的實現):
1,對於一個點咱們首先求出它所在的子樹大小,找到它的重兒子(即處理出size,son數組),
解釋:好比說點1,它有三個兒子2,3,4
2所在子樹的大小是5
3所在子樹的大小是2
4所在子樹的大小是6
那麼1的重兒子是4
ps:若是一個點的多個兒子所在子樹大小相等且最大
那隨便找一個當作它的重兒子就行了
葉節點沒有重兒子,非葉節點有且只有一個重兒子
2,在dfs過程當中順便記錄其父親以及深度(即處理出f,d數組),操做1,2能夠經過一遍dfs完成
void dfs1(int u,int fa,int depth) //當前節點、父節點、層次深度
{ f[u]=fa; d[u]=depth; size[u]=1; //這個點自己size=1
for(int i=head[u];i;i=e[i].next) { int v=e[i].to; if(v==fa) continue; dfs1(v,u,depth+1); //層次深度+1
size[u]+=size[v]; //子節點的size已被處理,用它來更新父節點的size
if(size[v]>size[son[u]]) son[u]=v; //選取size最大的做爲重兒子
} } //進入
dfs1(root,0,1);
dfs跑完大概是這樣的,你們能夠手動模擬一下
3,第二遍dfs,而後鏈接重鏈,同時標記每個節點的dfs序,而且爲了用數據結構來維護重鏈,咱們在dfs時保證一條重鏈上各個節點dfs序連續(即處理出數組top,id,rk)
void dfs2(int u,int t) //當前節點、重鏈頂端
{ top[u]=t; id[u]=++cnt; //標記dfs序
rk[cnt]=u; //序號cnt對應節點u
if(!son[u]) return; dfs2(son[u],t); /*咱們選擇優先進入重兒子來保證一條重鏈上各個節點dfs序連續, 一個點和它的重兒子處於同一條重鏈,因此重兒子所在重鏈的頂端仍是t*/
for(int i=head[u];i;i=e[i].next) { int v=e[i].to; if(v!=son[u]&&v!=f[u]) dfs2(v,v); //一個點位於輕鏈底端,那麼它的top必然是它自己
} }
dfs跑完大概是這樣的,你們能夠手動模擬一下
4,兩遍dfs就是樹鏈剖分的主要處理,經過dfs咱們已經保證一條重鏈上各個節點dfs序連續,那麼能夠想到,咱們能夠經過數據結構(以線段樹爲例)來維護一條重鏈的信息
回顧上文的那個題目,修改和查詢操做原理是相似的,以查詢操做爲例,其實就是個LCA,不過這裏使用了top來進行加速,由於top能夠直接跳轉到該重鏈的起始結點,輕鏈沒有起始結點之說,他們的top就是本身。須要注意的是,每次循環只能跳一次,而且讓結點深的那個來跳到top的位置,避免兩個一塊兒跳從而擦肩而過。
int sum(int x,int y) { int ans=0,fx=top[x],fy=top[y]; while(fx!=fy) //兩點不在同一條重鏈
{ if(d[fx]>=d[fy]) { ans+=query(id[fx],id[x],rt); //線段樹區間求和,處理這條重鏈的貢獻
x=f[fx],fx=top[x]; //將x設置成原鏈頭的父親結點,走輕邊,繼續循環
} else { ans+=query(id[fy],id[y],rt); y=f[fy],fy=top[y]; } } //循環結束,兩點位於同一重鏈上,但兩點不必定爲同一點,因此咱們還要統計這兩點之間的貢獻
if(id[x]<=id[y]) ans+=query(id[x],id[y],rt); else ans+=query(id[y],id[x],rt); return ans; }
你們若是明白了樹鏈剖分,也應該有觸類旁通的能力(反正我沒有),修改和LCA就留給你們本身完成了
5,樹鏈剖分的時間複雜度
樹鏈剖分的兩個性質:
1,若是(u, v)是一條輕邊,那麼size(v) < size(u)/2;
2,從根結點到任意結點的路所通過的輕重鏈的個數一定都小於logn;
能夠證實,樹鏈剖分的時間複雜度爲O(nlog^2n)
幾道例題:
1,樹鏈剖分模板
就是剛纔講的
上代碼:
#include<iostream> #include<cstdio> #define int long long using namespace std; const int maxn=1e5+10; struct edge{ int next,to; }e[maxn*2]; struct node{ int l,r,ls,rs,sum,lazy; }a[maxn*2]; int n,m,r,rt,mod,v[maxn],head[maxn],cnt,f[maxn],d[maxn],son[maxn],size[maxn],top[maxn],id[maxn],rk[maxn]; void add(int x,int y) { e[++cnt].next=head[x]; e[cnt].to=y; head[x]=cnt; } void dfs1(int x) { size[x]=1,d[x]=d[f[x]]+1; for(int v,i=head[x];i;i=e[i].next) if((v=e[i].to)!=f[x]) { f[v]=x,dfs1(v),size[x]+=size[v]; if(size[son[x]]<size[v]) son[x]=v; } } void dfs2(int x,int tp) { top[x]=tp,id[x]=++cnt,rk[cnt]=x; if(son[x]) dfs2(son[x],tp); for(int v,i=head[x];i;i=e[i].next) if((v=e[i].to)!=f[x]&&v!=son[x]) dfs2(v,v); } inline void pushup(int x) { a[x].sum=(a[a[x].ls].sum+a[a[x].rs].sum)%mod; } void build(int l,int r,int x) { if(l==r) { a[x].sum=v[rk[l]],a[x].l=a[x].r=l; return; } int mid=l+r>>1; a[x].ls=cnt++,a[x].rs=cnt++; build(l,mid,a[x].ls),build(mid+1,r,a[x].rs); a[x].l=a[a[x].ls].l,a[x].r=a[a[x].rs].r; pushup(x); } inline int len(int x) { return a[x].r-a[x].l+1; } inline void pushdown(int x) { if(a[x].lazy) { int ls=a[x].ls,rs=a[x].rs,lz=a[x].lazy; (a[ls].lazy+=lz)%=mod,(a[rs].lazy+=lz)%=mod; (a[ls].sum+=lz*len(ls))%=mod,(a[rs].sum+=lz*len(rs))%=mod; a[x].lazy=0; } } void update(int l,int r,int c,int x) { if(a[x].l>=l&&a[x].r<=r) { (a[x].lazy+=c)%=mod,(a[x].sum+=len(x)*c)%=mod; return; } pushdown(x); int mid=a[x].l+a[x].r>>1; if(mid>=l) update(l,r,c,a[x].ls); if(mid<r) update(l,r,c,a[x].rs); pushup(x); } int query(int l,int r,int x) { if(a[x].l>=l&&a[x].r<=r) return a[x].sum; pushdown(x); int mid=a[x].l+a[x].r>>1,tot=0; if(mid>=l) tot+=query(l,r,a[x].ls); if(mid<r) tot+=query(l,r,a[x].rs); return tot%mod; } inline int sum(int x,int y) { int ret=0; while(top[x]!=top[y]) { if(d[top[x]]<d[top[y]]) swap(x,y); (ret+=query(id[top[x]],id[x],rt))%=mod; x=f[top[x]]; } if(id[x]>id[y]) swap(x,y); return (ret+query(id[x],id[y],rt))%mod; } inline void updates(int x,int y,int c) { while(top[x]!=top[y]) { if(d[top[x]]<d[top[y]]) swap(x,y); update(id[top[x]],id[x],c,rt); x=f[top[x]]; } if(id[x]>id[y]) swap(x,y); update(id[x],id[y],c,rt); } signed main() { scanf("%lld%lld%lld%lld",&n,&m,&r,&mod); for(int i=1;i<=n;i++) scanf("%lld",&v[i]); for(int x,y,i=1;i<n;i++) { scanf("%lld%lld",&x,&y); add(x,y),add(y,x); } cnt=0,dfs1(r),dfs2(r,r); cnt=0,build(1,n,rt=cnt++); for(int op,x,y,k,i=1;i<=m;i++) { scanf("%lld",&op); if(op==1) { scanf("%lld%lld%lld",&x,&y,&k); updates(x,y,k); } else if(op==2) { scanf("%lld%lld",&x,&y); printf("%lld\n",sum(x,y)); } else if(op==3) { scanf("%lld%lld",&x,&y); update(id[x],id[x]+size[x]-1,y,rt); } else { scanf("%lld",&x); printf("%lld\n",query(id[x],id[x]+size[x]-1,rt)); } } return 0; }
2,[NOI2015]軟件包管理器
觀察到題目要求支持兩種操做
1,install x:表示安裝軟件包x
2,uninstall x:表示卸載軟件包x
對於操做一,咱們能夠統計x到根節點未安裝的軟件包的個數,而後區間修改成已安裝
對於操做二,咱們能夠統計x所在子樹已安裝軟件包的個數,而後將子樹修改成未安裝
上代碼:
#include<iostream> #include<cstdio>
#define int long long
using namespace std; const int maxn=1e5+10; struct edge{ int next,to; }e[2*maxn]; struct Node{ int l,r,ls,rs,sum,lazy; }node[2*maxn]; int rt,n,m,cnt,head[maxn]; int f[maxn],d[maxn],size[maxn],son[maxn],rk[maxn],top[maxn],tid[maxn]; int readn() { int x=0; char ch=getchar(); while(ch<'0'||ch>'9') ch=getchar(); while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+ch-'0'; ch=getchar(); } return x; } void add_edge(int x,int y) { e[++cnt].next=head[x]; e[cnt].to=y; head[x]=cnt; } void dfs1(int u,int fa,int depth) { f[u]=fa; d[u]=depth; size[u]=1; for(int i=head[u];i;i=e[i].next) { int v=e[i].to; if(v==fa) continue; dfs1(v,u,depth+1); size[u]+=size[v]; if(size[v]>size[son[u]]||!son[u]) son[u]=v; } } void dfs2(int u,int t) { top[u]=t; tid[u]=++cnt; rk[cnt]=u; if(!son[u]) return; dfs2(son[u],t); for(int i=head[u];i;i=e[i].next) { int v=e[i].to; if(v!=son[u]&&v!=f[u]) dfs2(v,v); } } void pushup(int x) { int lson=node[x].ls,rson=node[x].rs; node[x].sum=node[lson].sum+node[rson].sum; node[x].l=node[lson].l; node[x].r=node[rson].r; } void build(int li,int ri,int cur) { if(li==ri) { node[cur].ls=node[cur].rs=node[cur].lazy=-1; node[cur].l=node[cur].r=li; return; } int mid=(li+ri)>>1; node[cur].ls=cnt++; node[cur].rs=cnt++; build(li,mid,node[cur].ls); build(mid+1,ri,node[cur].rs); pushup(cur); } void pushdown(int x) { int lson=node[x].ls,rson=node[x].rs; node[lson].sum=node[x].lazy*(node[lson].r-node[lson].l+1); node[rson].sum=node[x].lazy*(node[rson].r-node[rson].l+1); node[lson].lazy=node[x].lazy; node[rson].lazy=node[x].lazy; node[x].lazy=-1; } void update(int li,int ri,int c,int cur) { if(li<=node[cur].l&&node[cur].r<=ri) { node[cur].sum=c*(node[cur].r-node[cur].l+1); node[cur].lazy=c; return; } if(node[cur].lazy!=-1) pushdown(cur); int mid=(node[cur].l+node[cur].r)>>1; if(li<=mid) update(li,ri,c,node[cur].ls); if(mid<ri) update(li,ri,c,node[cur].rs); pushup(cur); } int query(int li,int ri,int cur) { if(li<=node[cur].l&&node[cur].r<=ri) return node[cur].sum; if(node[cur].lazy!=-1) pushdown(cur); int tot=0; int mid=(node[cur].l+node[cur].r)>>1; if(li<=mid) tot+=query(li,ri,node[cur].ls); if(mid<ri) tot+=query(li,ri,node[cur].rs); return tot; } int sum(int x) { int ans=0; int fx=top[x]; while(fx) { ans+=tid[x]-tid[fx]-query(tid[fx],tid[x],rt)+1; update(tid[fx],tid[x],1,rt); x=f[fx]; fx=top[x]; } ans+=tid[x]-tid[0]-query(tid[0],tid[x],rt)+1; update(tid[0],tid[x],1,rt); return ans; } signed main() { n=readn(); for(int i=1;i<n;i++) { int x=readn(); add_edge(x,i); add_edge(i,x); } cnt=0; dfs1(0,-1,1); dfs2(0,0); cnt=0; rt=cnt++; build(1,n,rt); m=readn(); for(int i=1;i<=m;i++) { int x; string op; cin>>op; x=readn(); if(op=="install") printf("%lld\n",sum(x)); else if(op=="uninstall") { printf("%lld\n",query(tid[x],tid[x]+size[x]-1,rt)); update(tid[x],tid[x]+size[x]-1,0,rt); } } return 0; }
3,[SDOI2011]染色
有一些思惟含量的題
統計顏色段數量時不能簡單地區間加法
線段樹還應維護區間最左顏色和區間最右顏色
合併時
若是S(l,k)的右端與S(k+1,r)的左端顏色相同,那麼S(l,r)=S(l,k)+S(k+1,r)-1(減去重複的那一個)
不然S(l,r)=S(l,k)+S(k+1,r)正常合併