樹鏈剖分——入門及模板題

一篇很好的博客 http://www.javashuo.com/article/p-sepprrrc-mv.htmlhtml

樹鏈剖分:將樹分割成一條條鏈,而後按照dfs序進行維護c++

爲何要進行樹鏈剖分?
首先來講通常的dfs序:能夠想象普通的dfs序只能保證同一子樹的結點序號是連續的
可是這樣的dfs並無很好的性質,好比咱們要處理從root點到某個葉子結點上的路徑,普通的dfs序就沒法維護這樣的路徑
例如root有三個子節點a,b,c,子節點a有兩個兒子d,e 子節點b有兩個兒子f,g
如今咱們要訪問指定的一條路徑root->d,可是dfs時root可能先訪問的是b,而後再訪問a
這樣訪問路徑就必須經過暴力lca來進行dfs序的詢問,而暴力lca的複雜度會達到O(n)

那麼咱們要使用一種方法來優化向上爬的複雜度
咱們考慮dfs時先dfs到size最大的點那裏——稱爲重兒子,dfs到底後這條鏈就是重鏈
不被重鏈包含的邊稱爲輕邊
而後再去dfs其餘子節點,dfs的過程和根節點的過程同樣
那麼樹的dfs序就能夠當作是不少條不相交的重鏈,被輕鏈連在一塊兒

這樣的樹鏈有兩個性質:
輕邊(u,v) 那麼size[u]/2>size[v],且輕邊
從根節點到任意結點的路徑上的輕重鏈數量是logn級的
感性體會一下這兩個性質:要通過輕邊(u,v),說明v的結點個數不會超過size[u]/2
那麼每次出現一條輕邊,其下面的子樹規模都要除以2,因此到人以結點,輕邊的數量是logn級的
那麼可知輕重鏈的數量也是logn級的

有了這兩個性質,咱們再來看向上爬的問題,非根結點x到根節點的路徑上最多交替出現logn條重鏈
而重鏈的dfs序剛好是連在一塊兒的,那麼就能夠一塊兒維護了
因此這樣來看從x到根節點只要維護logn次便可

對於樹上任意兩點(x,y),只要維護兩條到根節點的路徑便可! 數組

第一次dfs處理出size數組,fa數組,重兒子數組son,deep數組(在向上爬的時候要用到,這裏順便處理了)
第二次dfs處理出輕重鏈:idx數組(即dfs序,最好再處理個反向的has數組,創建線段樹時要用),top數組(維護每一個點所在鏈的頂端) ide

樹鏈剖分最簡單的應用就是求lca啦!優化

 

一些數組的定義ui

int f[maxn]; //父親  int d[maxn]; //深度  int son[maxn]; //重兒子  int size[maxn]; //大小  int top[maxn]; //所在鏈的頂端  int id[maxn]; //dfs序  int rk[maxn]; //dfs序對應的結點編號  int cnt; //dfs的時間戳 

 

輕重鏈剖分spa

void dfs1(int x,int pre,int deep){ size[x]=1,d[x]=deep; for(int i=head[x];i!=-1;i=edge[i].nxt){ int y=edge[i].to; if(y==pre)continue; f[y]=x;dfs1(y,x,deep+1);size[x]+=size[y]; if(size[son[x]]<size[y])son[x]=y; } } void dfs2(int x,int tp){ top[x]=tp;id[x]=++cnt;rk[cnt]=x; if(son[x])dfs2(son[x],tp); for(int i=head[x];i!=-1;i=edge[i].nxt){ int y=edge[i].to; if(y!=son[x] && y!=f[x])dfs2(y,y); } }

更新任意一條鏈(x,y):加上某個值code

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],1,n,1,c); x=f[top[x]]; } if(id[x]>id[y])swap(x,y); update(id[x],id[y],1,n,1,c); }

詢問任意一條鏈(x,y):求和htm

ll Sum(int x,int y){ ll res=0; while(top[x]!=top[y]){ if(d[top[x]]<d[top[y]])swap(x,y); res=(res+query(id[top[x]],id[x],1,n,1))%mod; x=f[top[x]]; } if(id[x]>id[y])swap(x,y); return (res+query(id[x],id[y],1,n,1))%mod; }

而後是完整的代碼 https://www.luogu.org/problemnew/show/P3384blog

#include<bits/stdc++.h>
using namespace std; #define maxn 200006
#define ll long long
struct Edge{int to,nxt;}edge[maxn<<1]; ll n,m,head[maxn],tot,r,v[maxn],mod; void init(){memset(head,-1,sizeof head);tot=0;} void addedge(int u,int v){edge[tot].nxt=head[u],edge[tot].to=v;head[u]=tot++;} int f[maxn];//父親 
int d[maxn];//深度 
int son[maxn];//重兒子 
int size[maxn];//大小 
int top[maxn];//所在鏈的頂端 
int id[maxn];//dfs序 
int rk[maxn];//dfs序對應的結點編號 
int cnt;//dfs的時間戳 
 
void dfs1(int x,int pre,int deep){ size[x]=1,d[x]=deep; for(int i=head[x];i!=-1;i=edge[i].nxt){ int y=edge[i].to; if(y==pre)continue; f[y]=x;dfs1(y,x,deep+1);size[x]+=size[y]; if(size[son[x]]<size[y])son[x]=y; } } void dfs2(int x,int tp){ top[x]=tp;id[x]=++cnt;rk[cnt]=x; if(son[x])dfs2(son[x],tp); for(int i=head[x];i!=-1;i=edge[i].nxt){ int y=edge[i].to; if(y!=son[x] && y!=f[x])dfs2(y,y); } } #define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1 ll sum[maxn<<2],lazy[maxn<<2]; void pushup(int rt){sum[rt]=sum[rt<<1]+sum[rt<<1|1];} void pushdown(int l,int r,int rt){ if(lazy[rt]){ int m=l+r>>1; sum[rt<<1]=(sum[rt<<1]+lazy[rt]*(m-l+1)%mod)%mod; sum[rt<<1|1]=(sum[rt<<1|1]+lazy[rt]*(r-m)%mod)%mod; lazy[rt<<1]=(lazy[rt<<1]+lazy[rt])%mod; lazy[rt<<1|1]=(lazy[rt<<1|1]+lazy[rt])%mod; lazy[rt]=0; } } void build(int l,int r,int rt){ if(l==r){sum[rt]=v[rk[l]];return;} int m=l+r>>1; build(lson);build(rson); pushup(rt); } void update(int L,int R,int l,int r,int rt,ll val){ if(L<=l && R>=r){ lazy[rt]=(lazy[rt]+val)%mod; sum[rt]=(sum[rt]+val*(r-l+1))%mod; return; } pushdown(l,r,rt); int m=l+r>>1; if(L<=m)update(L,R,lson,val); if(R>m)update(L,R,rson,val); pushup(rt); } ll query(int L,int R,int l,int r,int rt){ if(L<=l && R>=r)return sum[rt]; pushdown(l,r,rt); int m=l+r>>1;ll res=0; if(L<=m)res=(res+query(L,R,lson))%mod; if(R>m)res=(res+query(L,R,rson))%mod; return res; } ll Sum(int x,int y){ ll res=0; while(top[x]!=top[y]){ if(d[top[x]]<d[top[y]])swap(x,y); res=(res+query(id[top[x]],id[x],1,n,1))%mod; x=f[top[x]]; } if(id[x]>id[y])swap(x,y); return (res+query(id[x],id[y],1,n,1))%mod; } 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],1,n,1,c); x=f[top[x]]; } if(id[x]>id[y])swap(x,y); update(id[x],id[y],1,n,1,c); } int main(){ init(); scanf("%lld%lld%lld%lld",&n,&m,&r,&mod); for(int i=1;i<=n;i++)scanf("%lld",&v[i]); ll x,y; for(int j=1;j<n;j++){ scanf("%lld%lld",&x,&y); addedge(x,y);addedge(y,x); } cnt=0;dfs1(r,0,1);dfs2(r,r); build(1,n,1); while(m--){ ll op,x,y,k; scanf("%lld",&op); if(op==1){//x->y路徑上+k
            scanf("%lld%lld%lld",&x,&y,&k); updates(x,y,k); } else if(op==2){//x->y上的sum 
            scanf("%lld%lld",&x,&y); cout<<Sum(x,y)<<endl; } else if(op==3){//x子樹上的全部點都+k
            scanf("%lld%lld",&x,&k); update(id[x],id[x]+size[x]-1,1,n,1,k); } else {//查詢x子樹下的和 
            scanf("%lld",&x); cout<<query(id[x],id[x]+size[x]-1,1,n,1)<<endl; } } }
View Code
相關文章
相關標籤/搜索