1. 程式人生 > 其它 >學習筆記——樹鏈剖分

學習筆記——樹鏈剖分

樹鏈剖分學習筆記

思想

對於每個節點,把所有子節點中子樹最大的一個,成為重點,其它成為輕點。重點到父親節點的連線成為重邊,重邊連線成若干條重鏈,其餘的每個點稱為重鏈。

可以發現,如果路徑經過一條輕邊,那麼現在的子樹大小至少縮小一半,所以每條路徑可以被拆分成最多 \(\log n\) 條鏈,這樣一來,就可以把較難維護的樹形結構,改變成若干條鏈,並且可以發現按照先重後輕的原則遍歷時,屬於相同重鏈或相同子樹的遍歷序是連續的,就可以根據這個性質用線段樹維護。

每個鏈線上段樹上的區間是 \([dfn(top(u)),dfn(u)]\),每個子樹線上段樹上的區間是 \([dfn(u),dfn(u)+siz(u)-1]\),根據這個進行線段樹操作。

模板

P3384 輕重鏈剖分/樹鏈剖分

Code

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<queue>
#include<stack>
#include<vector>
#include<map>
using namespace std;
#define re register
typedef int ll;
typedef double db;
typedef unsigned long long ull;
const int maxn=1e6+10;
const int maxm=1e6+10;
const ll mod=1e9+7;
const ull base1=19260817;
const ull base2=19660813;
const ll maxxn=0x7ffffff;
const ll minxn=-0x7ffffff;
const db inf=1e13;
#define mid ((l+r)>>1)
#define lson rt<<1,l,mid
#define rson rt<<1|1,mid+1,r
#define len (r-l+1)
inline ll read(){
    ll x=0,w=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
    while(c<='9'&&c>='0'){x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return x*w;
}
inline void write(ll x){
    if(x<0){putchar('-');write(-x);return;}
    if(x>=10) write(x/10);
    putchar(x%10+'0');
}
ll n,m,root,p;
struct edge{
    ll to,nxt;
}e[maxn];
ll cnt,head[maxn];
ll w[maxn],dfnw[maxn];
ll fa[maxn],son[maxn],dep[maxn],siz[maxn];
ll dfn[maxn],top[maxn],dfncnt;
inline void add_edge(ll u,ll v){
    e[++cnt].to=v;
    e[cnt].nxt=head[u];
    head[u]=cnt;
}
//第一次dfs遍歷深度,父親,子樹大小和重兒子
inline void dfs1(ll u,ll f,ll d){
    dep[u]=d,fa[u]=f,siz[u]=1;
    ll maxson=-1;
    for(int i=head[u];i;i=e[i].nxt){
        ll v=e[i].to;
        if(v==f) continue;
        dfs1(v,u,d+1);
        siz[u]+=siz[v];
        if(siz[v]>maxson){
            son[u]=v;
            maxson=siz[v];
        }
    }
}
//第二次dfs遍歷重鏈的頂點和dfs序
inline void dfs2(ll u,ll t){
    dfn[u]=++dfncnt,dfnw[dfncnt]=w[u],top[u]=t;
    if(!son[u]) return;
    dfs2(son[u],t);//先遍歷重兒子保證dfs序連續
    for(int i=head[u];i;i=e[i].nxt){
        ll v=e[i].to;
        if(v==fa[u]||v==son[u]) continue;
        dfs2(v,v);//輕兒子是所在重鏈的頂點
    }
}
ll num[maxn],laz[maxn];
//線段樹維護
struct XDS{
    inline void build(ll rt,ll l,ll r){
        if(l==r){
            num[rt]=dfnw[l]%p;
            return;
        }
        build(lson),build(rson);
        num[rt]=(num[rt<<1]+num[rt<<1|1])%p;
    }
    inline void push_down(ll rt,ll l,ll r){
        laz[rt<<1]+=laz[rt],laz[rt<<1|1]+=laz[rt];
        num[rt<<1]+=laz[rt]*(mid-l+1),num[rt<<1|1]+=laz[rt]*(r-mid);
        num[rt<<1]%=p,num[rt<<1|1]%=p,laz[rt]=0;
    }
    inline void update(ll rt,ll l,ll r,ll pl,ll pr,ll k){
        if(pl<=l&&r<=pr){
            laz[rt]+=k;
            num[rt]+=k*len;
        }
        else{
            if(laz[rt]) push_down(rt,l,r);
            if(pl<=mid) update(lson,pl,pr,k);
            if(pr>mid) update(rson,pl,pr,k);
            num[rt]=(num[rt<<1]+num[rt<<1|1])%p;
        }
    }
    inline ll query(ll rt,ll l,ll r,ll pl,ll pr){
        if(pl<=l&&r<=pr){
            return num[rt]%p;
        }
        else{
            if(laz[rt]) push_down(rt,l,r);
            ll sum=0;
            if(pl<=mid) sum+=query(lson,pl,pr);
            if(pr>mid) sum+=query(rson,pl,pr);
            return sum;
        }
    }
}tree;
//處理區間時,因為一定在一條重鏈向下延伸的重鏈中,所以一直向上跳深度較大的一點直到lca
inline void update_pt(ll x,ll y,ll k){
    k%=p;
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]]) swap(x,y);
        tree.update(1,1,n,dfn[top[x]],dfn[x],k);
        x=fa[top[x]];
    }
    if(dep[x]>dep[y]) swap(x,y);
    tree.update(1,1,n,dfn[x],dfn[y],k);
}
inline ll query_pt(ll x,ll y){
    ll sum=0;
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]]) swap(x,y);
        sum=(sum+tree.query(1,1,n,dfn[top[x]],dfn[x]))%p;
        x=fa[top[x]];
    }
    if(dep[x]>dep[y]) swap(x,y);
    sum=(sum+tree.query(1,1,n,dfn[x],dfn[y]))%p;
    return sum%p;
}
//處理子樹時,因為dfs序一定是連續的,所以與線段樹區間處理相同
inline void update_tr(ll x,ll k){
    tree.update(1,1,n,dfn[x],dfn[x]+siz[x]-1,k);
}
inline ll query_tr(ll x){
    return tree.query(1,1,n,dfn[x],dfn[x]+siz[x]-1)%p;
}
int main(){
    n=read(),m=read(),root=read(),p=read();
    for(int i=1;i<=n;i++){
        w[i]=read();
    }
    for(int i=1;i<n;i++){
        ll u=read(),v=read();
        add_edge(u,v),add_edge(v,u);
    }
    dfs1(root,0,1);
    dfs2(root,root);
    tree.build(1,1,n);
    while(m--){
        ll opt=read();
        if(opt==1){
            ll x=read(),y=read(),k=read();
            update_pt(x,y,k);
        }
        else if(opt==2){
            ll x=read(),y=read();
            printf("%d\n",query_pt(x,y)%p);
        }
        else if(opt==3){
            ll x=read(),k=read();
            update_tr(x,k);
        }
        else{
            ll x=read();
            printf("%d\n",query_tr(x)%p);
        }
    }
}

例題

1.P2690 ZJOI2008 書的統計

甚至比模板還要簡單,沒有複雜的轉換,只需要維護區間和以及區間最大值,直接樹剖套線段樹。

程式碼鴿了。

2.P2486 SDOI2011 染色

一道很有趣的題。

因為要維護連續顏色段數,線段樹的查詢和修改就略有不同,函式引數應為:當前下標,當前區間,當前查詢區間,要求查詢區間,這樣能夠維護出邊界的顏色,來特判左右兩段是否合併為一個。

ACcode

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<queue>
#include<stack>
#include<vector>
#include<map>
using namespace std;
#define re register
typedef int ll;
typedef double db;
typedef unsigned long long ull;
const int maxn=1e6+10;
const int maxm=1e6+10;
const ll mod=1e9+7;
const ull base1=19260817;
const ull base2=19660813;
const ll maxxn=0x7ffffff;
const ll minxn=-0x7ffffff;
const db inf=1e13;
#define mid ((l+r)>>1)
#define lson rt<<1,l,mid
#define rson rt<<1|1,mid+1,r
#define len (r-l+1)
inline ll read(){
    ll x=0,w=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
    while(c<='9'&&c>='0'){x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return x*w;
}
inline void write(ll x){
    if(x<0){putchar('-');write(-x);return;}
    if(x>=10) write(x/10);
    putchar(x%10+'0');
}
ll n,m;
struct edge{
    ll to,nxt;
}e[maxn];
ll cnt,head[maxn];
inline void add_edge(ll u,ll v){
    e[++cnt].to=v;
    e[cnt].nxt=head[u];
    head[u]=cnt;
}
ll w[maxn];
ll fa[maxn],son[maxn],dep[maxn],siz[maxn];
ll dfn[maxn],top[maxn],dfncnt;
inline void dfs1(ll u,ll f,ll d){
    dep[u]=d,fa[u]=f,siz[u]=1;
    ll maxson=-1;
    for(int i=head[u];i;i=e[i].nxt){
        ll v=e[i].to;
        if(v==f) continue;
        dfs1(v,u,d+1);
        siz[u]+=siz[v];
        if(siz[v]>maxson){
            son[u]=v;
            maxson=siz[v];
        }
    }
}
inline void dfs2(ll u,ll t){
    dfn[u]=++dfncnt,top[u]=t;
    if(!son[u]) return;
    dfs2(son[u],t);
    for(int i=head[u];i;i=e[i].nxt){
        ll v=e[i].to;
        if(v==fa[u]||v==son[u]) continue;
        dfs2(v,v);
    }
}
ll num[maxn],laz[maxn],lc[maxn],rc[maxn];
ll cl,cr;
struct XDS{
	inline void push_down(ll rt){
		if(laz[rt]){
			laz[rt<<1]=laz[rt<<1|1]=laz[rt];
			num[rt<<1]=num[rt<<1|1]=1;
			lc[rt<<1]=rc[rt<<1]=lc[rt];
			lc[rt<<1|1]=rc[rt<<1|1]=lc[rt];
			laz[rt]=0;
		}
	}
	inline void push_up(ll rt){
		lc[rt]=lc[rt<<1],rc[rt]=rc[rt<<1|1];
		ll ans=num[rt<<1]+num[rt<<1|1];
		if(rc[rt<<1]==lc[rt<<1|1]) ans--;
		num[rt]=ans;
	}
	inline void build(ll rt,ll l,ll r){
		num[rt]=0;
		if(l==r){
			return;
		}
		build(lson),build(rson);
	}
	inline void update(ll rt,ll l,ll r,ll nl,ll nr,ll k){
		if(nl==l&&r==nr){
			num[rt]=laz[rt]=1;
			lc[rt]=rc[rt]=k;
			return;
		}
		push_down(rt);
		if(nr<=mid) update(lson,nl,nr,k);
		else if(nl>mid) update(rson,nl,nr,k);
		else{
			update(lson,nl,mid,k),update(rson,mid+1,nr,k);
		}
		push_up(rt);
	}
	inline ll query(ll rt,ll l,ll r,ll nl,ll nr,ll pl,ll pr){
		if(l==pl) cl=lc[rt];
		if(r==pr) cr=rc[rt];
		if(nl==l&&r==nr){
			return num[rt];
		}
		push_down(rt);
		if(pr<=mid) return query(lson,nl,nr,pl,pr);
		else if(pl>mid) return query(rson,nl,nr,pl,pr);
		else{
			ll ans=query(lson,nl,mid,pl,pr)+query(rson,mid+1,nr,pl,pr);
			if(rc[rt<<1]==lc[rt<<1|1]) ans--;
			return ans;
		}
		push_up(rt);
	}
}tree;
inline void update_pt(ll u,ll v,ll k){
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		tree.update(1,1,n,dfn[top[u]],dfn[u],k);
		u=fa[top[u]];
	}
	if(dep[u]>dep[v]) swap(u,v);
	tree.update(1,1,n,dfn[u],dfn[v],k);
}
inline ll query_pt(ll u,ll v){
	ll ansl=-1,ansr=-1;
	ll ans=0;
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]]) swap(u,v),swap(ansl,ansr);
		ans+=tree.query(1,1,n,dfn[top[u]],dfn[u],dfn[top[u]],dfn[u]);
		if(cr==ansl) ans--;
		ansl=cl;
		u=fa[top[u]];
	}
	if(dep[u]<dep[v]) swap(u,v),swap(ansl,ansr);
	ans+=tree.query(1,1,n,dfn[v],dfn[u],dfn[v],dfn[u]);
	if(cr==ansl) ans--;
	if(cl==ansr) ans--;
	return ans;
}
int main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++){
		w[i]=read();
	}
	for(int i=1;i<n;i++){
		ll u=read(),v=read();
		add_edge(u,v),add_edge(v,u);
	}
	dfs1(1,0,1);
	dfs2(1,1);
	tree.build(1,1,n);
	for(int i=1;i<=n;i++){
		tree.update(1,1,n,dfn[i],dfn[i],w[i]);
	}
	while(m--){
		char opt;
		cin>>opt;
		if(opt=='C'){
			ll u=read(),v=read(),k=read();
			update_pt(u,v,k);
		}
		else{
			ll u=read(),v=read();
			printf("%d\n",query_pt(u,v));
		}
	}
}

3.P2590 ZJOI2008 樹的統計

模板題,程式碼不貼了。

4.P1967 貨車運輸

\(\operatorname{Kruskal}\)最大生成樹重構一下,可以放到樹剖裡跑,因為維護的是樹上邊權最小值,可以把邊權掛在子節點上,處理在同一重鏈的 \([L,R]\) 的路徑時,線段樹查詢 \([L+1,R]\) 的最小值即可。

兩個\(\operatorname{dfs}\)

ll fa[maxn],son[maxn],dep[maxn],siz[maxn];
ll dfn[maxn],top[maxn],tow[maxn],dfnw[maxn],dfncnt;
inline void dfs1(ll u,ll f,ll d){
	dep[u]=d,fa[u]=f,siz[u]=1;
	ll maxson=-1;
	for(int i=head[u];i;i=e[i].nxt){
		ll v=e[i].to;
		if(v==f) continue;
		tow[v]=e[i].w;
		dfs1(v,u,d+1);
		siz[u]+=siz[v];
		if(siz[v]>maxson){
			son[u]=v;
			maxson=siz[v];
		}
	}
}
inline void dfs2(ll u,ll t){
	dfn[u]=++dfncnt,dfnw[dfncnt]=tow[u],top[u]=t;
	if(!son[u]) return;
	dfs2(son[u],t);
	for(int i=head[u];i;i=e[i].nxt){
		ll v=e[i].to;
		if(v==fa[u]||v==son[u]) continue;
		dfs2(v,v);
	}
}

查詢操作

inline ll query_ptmin(ll u,ll v){
	ll ans=maxxn;
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		ans=min(ans,tree.query_min(1,1,n,dfn[top[u]],dfn[u]));
		u=fa[top[u]];
	}
	if(dep[u]<dep[v]) swap(u,v);
	ans=min(ans,tree.query_min(1,1,n,dfn[v]+1,dfn[u]));
	return ans;
}

5.P3979 遙遠的國度

換根的樹剖,支援區間修改和區間維護最小值。

對於換根,因為樹的性質不變,所以如果在初始化以 \(1\) 為根的樹中,現在根節點和查詢節點同屬一顆子樹的同左或同右的子樹,就直接查詢這個子樹,如果在異側那就找到子樹的根,然後查詢兩次。

ACcode

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<queue>
#include<stack>
#include<vector>
#include<map>
using namespace std;
#define re register
typedef long long ll;
typedef double db;
typedef unsigned long long ull;
const int maxn=1e5+10;
const int maxm=4e5+10;
const ll mod=1e9+7;
const ull base1=19260817;
const ull base2=19660813;
const ll maxxn=0x7fffffff;
const ll minxn=-0x7fffffff;
const db inf=1e13;
#define mid ((l+r)>>1)
#define lson rt<<1,l,mid
#define rson rt<<1|1,mid+1,r
#define len (r-l+1)
inline ll read(){
    ll x=0,w=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
    while(c<='9'&&c>='0'){x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return x*w;
}
inline void write(ll x){
    if(x<0){putchar('-');write(-x);return;}
    if(x>=10) write(x/10);
    putchar(x%10+'0');
}
ll n,m,root;
struct edge{
	ll to,nxt;
}e[maxm];
ll head[maxn],cnt;
inline void add_edge(ll u,ll v){
	e[++cnt].to=v;
	e[cnt].nxt=head[u];
	head[u]=cnt;
}
ll w[maxn];
ll fa[maxn],son[maxn],dep[maxn],siz[maxn];
ll dfn[maxn],top[maxn],dfncnt,dfnw[maxn];
inline void dfs1(ll u,ll f,ll d){
	fa[u]=f,dep[u]=d,siz[u]=1;
	ll maxson=-1;
	for(int i=head[u];i;i=e[i].nxt){
		ll v=e[i].to;
		if(v==f) continue;
		dfs1(v,u,d+1);
		siz[u]+=siz[v];
		if(siz[v]>maxson){
			maxson=siz[v];
			son[u]=v;
		}
	}
}
inline void dfs2(ll u,ll t){
	dfn[u]=++dfncnt,dfnw[dfncnt]=w[u],top[u]=t;
	if(!son[u]) return;
	dfs2(son[u],t);
	for(int i=head[u];i;i=e[i].nxt){
		ll v=e[i].to;
		if(v==fa[u]||v==son[u]) continue;
		dfs2(v,v);
	}	
}
ll minx[maxm],laz[maxm];
struct XDS{
	inline void push_up(ll rt){
		minx[rt]=min(minx[rt<<1],minx[rt<<1|1]);
	}
	inline void build(ll rt,ll l,ll r){
		if(l==r){
			minx[rt]=dfnw[l];
			return;
		}
		build(lson),build(rson);
		push_up(rt);
	}
	inline void push_down(ll rt){
		if(laz[rt]){
			laz[rt<<1]=laz[rt<<1|1]=laz[rt];
			minx[rt<<1]=minx[rt<<1|1]=laz[rt];
			laz[rt]=0;
		}
	}
	inline void updatex(ll rt,ll l,ll r,ll ql,ll qr,ll k){
		if(ql<=l&&r<=qr){
			minx[rt]=laz[rt]=k;
			return;
		}
		push_down(rt);
		if(ql<=mid) updatex(lson,ql,qr,k);
		if(qr>mid) updatex(rson,ql,qr,k);
		push_up(rt);
	}
	inline ll queryx(ll rt,ll l,ll r,ll ql,ll qr){
		if(ql<=l&&r<=qr){
			return minx[rt];
		}
		ll ans=maxxn;
		push_down(rt);
		if(ql<=mid) ans=min(ans,queryx(lson,ql,qr));
		if(qr>mid) ans=min(ans,queryx(rson,ql,qr));
		push_up(rt);
		return ans;
	}
}tree;
inline void update_pt(ll u,ll v,ll k){
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		tree.updatex(1,1,n,dfn[top[u]],dfn[u],k);
		u=fa[top[u]];
	}
	if(dep[u]>dep[v]) swap(u,v);
	tree.updatex(1,1,n,dfn[u],dfn[v],k);
}
inline ll find(ll u){
	if(u==root) return -1;
	if(dfn[u]>=dfn[root]||dfn[u]+siz[u]-1<dfn[root]) return 0;
	ll tmp=root;
	while(top[tmp]!=top[u]){
		if(fa[top[tmp]]==u) return top[tmp];
		tmp=fa[top[tmp]];
	}
	return son[u];
}
inline ll query_tr(ll u){
	ll ty=find(u);
	if(ty==-1) return minx[1];
	if(!ty) return tree.queryx(1,1,n,dfn[u],dfn[u]+siz[u]-1);
	else{
		ll ans=tree.queryx(1,1,n,1,dfn[ty]-1);
		if(dfn[ty]+siz[ty]-1!=n){
			ans=min(ans,tree.queryx(1,1,n,dfn[ty]+siz[ty],n));
		}
		return ans;
	}
}
int main(){
	n=read(),m=read();
	for(int i=1;i<n;i++){
		ll u=read(),v=read();
		add_edge(u,v),add_edge(v,u);
	}
	for(int i=1;i<=n;i++){
		w[i]=read();
	}
	dfs1(1,0,1);
	dfs2(1,1);
	tree.build(1,1,n);
	root=read();
	for(int i=1;i<=m;i++){
		ll opt=read();
		if(opt==1) root=read();
		else if(opt==2){
			ll u=read(),v=read(),k=read();
			update_pt(u,v,k);
		}
		else{
			ll u=read();
			printf("%lld\n",query_tr(u));
		}
	}
	return 0;
}

5.P1505 國家集訓隊 旅遊

多用懶標記維護幾個東西就ok了,因為要維護最大值以及最小值,所以取負的時候直接交換最大最小值。

放個上傳和下傳程式碼

inline void push_up(ll rt){
	maxx[rt]=max(maxx[rt<<1],maxx[rt<<1|1]);
	minx[rt]=min(minx[rt<<1],minx[rt<<1|1]);
	sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
inline void push_down(ll rt){
	laz[rt<<1]^=1,laz[rt<<1|1]^=1;
	maxx[rt<<1]=-maxx[rt<<1],maxx[rt<<1|1]=-maxx[rt<<1|1];
	minx[rt<<1]=-minx[rt<<1],minx[rt<<1|1]=-minx[rt<<1|1];
	sum[rt<<1]=-sum[rt<<1],sum[rt<<1|1]=-sum[rt<<1|1];
	swap(maxx[rt<<1],minx[rt<<1]);
	swap(maxx[rt<<1|1],minx[rt<<1|1]);
	laz[rt]=0;
}

6.P3258 JLOI2014 松鼠的新家

區間修改+單點查詢

注意每次走的終點要減\(1\),因為會重複。

7.P2146 NOI2015 軟體包管理器

  • 安裝操作:把根節點到當前節點路徑賦值為\(1\)
  • 解除安裝操作:當前節點子樹賦值為\(0\)

輸出變化量。