樹剖 - 其實並沒有那麼難

樹剖

將樹剖分爲鏈,然後我們就可以利用我們非常熟悉的線段樹來做了

例題:luogu3384
ACcode

#define mid ((l+r)>>1)
#define lson rt<<1,l,mid
#define rson rt<<1|1,mid+1,r
#define len (r-l+1)

const int maxn=200000+10;
int n,m,r,mod;
int e,beg[maxn],nex[maxn],to[maxn],w[maxn],wt[maxn];
int a[maxn<<2],laz[maxn<<2];
int son[maxn],id[maxn],fa[maxn],cnt,dep[maxn],siz[maxn],top[maxn]; 
int res=0;

inline void add(int x,int y){
    to[++e]=y;
    nex[e]=beg[x];
    beg[x]=e;
}
inline void pushdown(int rt,int lenn){
    laz[rt<<1]+=laz[rt];
    laz[rt<<1|1]+=laz[rt];
    a[rt<<1]+=laz[rt]*(lenn-(lenn>>1));
    a[rt<<1|1]+=laz[rt]*(lenn>>1);
    a[rt<<1]%=mod;
    a[rt<<1|1]%=mod;
    laz[rt]=0;
}

inline void build(int rt,int l,int r){
    if(l==r){
        a[rt]=wt[l];
        if(a[rt]>mod)a[rt]%=mod;
        return;
    }
    build(lson);
    build(rson);
    a[rt]=(a[rt<<1]+a[rt<<1|1])%mod;
}

inline void query(int rt,int l,int r,int L,int R){
    if(L<=l&&r<=R){res+=a[rt];res%=mod;return;}
    else{
        if(laz[rt])pushdown(rt,len);
        if(L<=mid)query(lson,L,R);
        if(R>mid)query(rson,L,R);
    }
}

inline void update(int rt,int l,int r,int L,int R,int k){
    if(L<=l&&r<=R){
        laz[rt]+=k;
        a[rt]+=k*len;
    }
    else{
        if(laz[rt])pushdown(rt,len);
        if(L<=mid)update(lson,L,R,k);
        if(R>mid)update(rson,L,R,k);
        a[rt]=(a[rt<<1]+a[rt<<1|1])%mod;
    }
}
inline int qRange(int x,int y){
    int ans=0;
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]])swap(x,y);
        res=0;
        query(1,1,n,id[top[x]],id[x]);
        ans+=res;
        ans%=mod;
        x=fa[top[x]];
    }
    if(dep[x]>dep[y])swap(x,y);
    res=0;
    query(1,1,n,id[x],id[y]);
    ans+=res;
    return ans%mod;
}

inline void updRange(int x,int y,int k){
    k%=mod;
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]])swap(x,y);
        update(1,1,n,id[top[x]],id[x],k);
        x=fa[top[x]];
    }
    if(dep[x]>dep[y])swap(x,y);
    update(1,1,n,id[x],id[y],k);
}

inline int qSon(int x){
    res=0;
    query(1,1,n,id[x],id[x]+siz[x]-1);
    return res;
}

inline void updSon(int x,int k){
    update(1,1,n,id[x],id[x]+siz[x]-1,k);
}

inline void dfs1(int x,int f,int deep){ 
    dep[x]=deep;
    fa[x]=f;
    siz[x]=1;
    int maxson=-1;
    for(Rint i=beg[x];i;i=nex[i]){
        int y=to[i];
        if(y==f)continue;
        dfs1(y,x,deep+1);
        siz[x]+=siz[y];
    }
}

inline void dfs2(int x,int topf){//x當前節點,topf當前鏈的最頂端的節點 
    id[x]=++cnt;//標記每個點的新編號 
    wt[cnt]=w[x];//把每個點的初始值賦到新編號上來 
    top[x]=topf;//這個點所在鏈的頂端 
    if(!son[x])return;//如果沒有兒子則返回 
    dfs2(son[x],topf);//按先處理重兒子,再處理輕兒子的順序遞歸處理 
    for(Rint i=beg[x];i;i=nex[i]){
        int y=to[i];
        if(y==fa[x]||y==son[x])continue;
        dfs2(y,y);//對於每一個輕兒子都有一條從它自己開始的鏈 
    }
}

int main(){
    read(n);read(m);read(r);read(mod);
    for(Rint i=1;i<=n;i++)read(w[i]);
    for(Rint i=1;i<n;i++){
        int a,b;
        read(a);read(b);
        add(a,b);add(b,a);
    }
    dfs1(r,0,1);
    dfs2(r,r);
    build(1,1,n);
    while(m--){
        int k,x,y,z;
        read(k);
        if(k==1){
            read(x);read(y);read(z);
            updRange(x,y,z);
        }
        else if(k==2){
            read(x);read(y);
            printf("%d\n",qRange(x,y));
        }
        else if(k==3){
            read(x);read(y);
            updSon(x,y);
        }
        else{
            read(x);
            printf("%d\n",qSon(x));
        }
    }
}

Tip:可能用到的運算級別 ! > & > |

樹剖裏的線段樹

把樹狀結構轉化爲序列問題的想法是不是很妙啊?
gun!
線段樹都還沒學明白就想剛樹剖?滾回去學線段樹、、

線段樹的節點就按二叉樹寫啦,不需要結構體

最好寫到namespace裏面
namespace可好用了:

namespace SGT{
	const int NNN=::NNN<<2;//裏面的是NNN,外面的是::NNN,線段樹要開4倍 
	ll tree[NNN],lazy[NNN];
	 
	inline void pushup(int p){tree[p]=(tree[p<<1]+tree[p<<1|1])%mod;}
	inline void pushdown(int p,int len/*=r-l+1,注意多個lazy要打*/){
		lazy[p<<1]=lazy[p];
		lazy[p<<1|1]=lazy[p];
		tree[p<<1]+=lazy[p]*(len-(len>>1));
		tree[p<<1|1]+=lazy[p]*(len>>1);
		tree[p<<1]%=mod;
		tree[p<<1|1]%=mod;
	}
	
	inline void build(int p,int l,int r){
		if(l==r){
			tree[p]=wt[l];
			if(wt[l]>mod)tree[p]%=mod;
			return ;
		}
		int mid=(l+r)>>1;
		build(p<<1,l,mid);
		build(p<<1|1,mid+1,r);
		pushup(p);
	}
	
	inline void update(int p,int l,int r,int L,int R,int k){
		if(L<=l&&r<=R){
			tree[p]+=k*(r-l+1);
			lazy[p]+=k;
			return ;
		}
		
		int mid=(l+r)>>1;
		if(lazy[p])pushdown(p,r-l+1);
		if(L<=mid)update(p<<1,l,mid,L,R,k);
		if(R>mid)Update(p<<1|1,mid+1,r,L,R,k);
		pushup(p);
	}
	
	inline int query(int p,int l,int r,int L,int R){
		int res=0;
		if(L<=l&&r<=R){
			pushdown(p,r-l+1);
			res+=tree[p];
			res%=mod;
			return res;
		}
		
		int mid=(l+r)>>1;
		if(L<=mid)res+=query(p<<1,l,mid,L,R);
		if(R>mid)res+=query(p<<1|1,mid+1,r,L,R);
		return res;
	}
}

狂打50多行線段樹,爽!

輕重鏈剖分

前置

根據以子節點爲根的子樹個數size,size最大的爲重邊,其餘爲輕邊

輕重鏈有如下性質:(記uvu\to vee,其中uu爲父節點,vv爲子節點)

  1. 如果ee爲輕邊,那麼size(v)<size(u)/2size(v)<size(u)/2

  2. 從根到某點tt如果輕邊個數不大於logn\log n

    說明:由1.,每經過一條輕邊,節點個數減少至少一半,故最多經過logn\log n條輕邊

  3. 我們稱某條路徑爲重路徑(重鏈),當且僅當它全部由重邊組成
    容易發現,那麼對於每個點到根的路徑上都不超過logn\log n條輕邊和logn\log n條重路徑
    一個點在且只在一條重路徑上,而每條重路徑一定是一條從根結點方向向葉結點方向延伸的深度遞增的路徑
    你管它怎麼發現的,我也不知道

然鵝在算法中這些似乎沒有用到

準備工作

算法核心在於計算這幾個值:

num[i]:在序列中的i位置的下標
top[i]:i所在重路徑的頂部結點
son[i]:i深度最小的重兒子
size[i]:i的子樹結點數
dep[i]:i在樹中的深度
father[i]:i在樹中的父親

兩次DFS實現:

  1. 計算size, dep, father, son
  2. 計算top, num
inline void pre_DFS(int u,int fa,int depth){
	dep[u]=depth;
	father[u]=fa;
	size[u]=1;//一個葉子節點也是一棵子樹 
	int heavy_size=-1;
	for(int v:G[u]){
		if(!(v^fa))continue;
		pre_DFS(v,u,depth+1);
		size[u]+=size[v];
		if(size[v]>heavy_size)heavy_size=size[v],son[u]=v;
	}
}

inline void DFS(int u,int t/*top*/){
	num[u]=++cnt;
	top[u]=t;
	wt[cnt]=w[u];
	if(!son[u])return ;
	DFS(son[u],t);
	for(int v:G[u]){
		if(!(v^son[u])||!(v^father[u]))continue;
		DFS(v,v);
	}
}

這兩步與建樹,都是前置準備工作:

inline void pre_work(){
	pre_DFS(root,0,1);
	DFS(root,root);
    SGT::build(1,1,n);
}

先DFS再build,不然你沒有東西build

更新鏈

利用重鏈,跳LCA:
每次較深的那個點往上跳,直到兩點的top一樣
由於top一致的點一定都在一條重路徑上,那麼深度淺的那個點就是LCA

Tips:
要跳鏈頂更深的
最後兩點沒有相遇!所以結束的時候兩個點還要在update一次
num[top[u]]<num[u],在寫update的時候要小心

inline void chain_updating(int u,int v,int k){
	k%=mod;
	while(top[u]!=top[v]){
		if(dep[num[u]]<dep[num[v]])swap(u,v);
		SGT::update(1,1,n,num[top[u]],num[u],k);
		u=father[top[u]];
	}
	if(dep[u]>dep[v])swap(u,v);
	SGT::update(1,1,n,num[u],num[v],k);
	return ;
}

inline int chain_query(int u,int v){
	int res=0;
	while(top[u]!=top[v]){
		if(dep[num[u]]<dep[num[v]])swap(u,v);
		res+=SGT::query(1,1,n,num[top[u]],num[u]);
		res%=mod;
		u=father[top[u]];
	}
	if(dep[u]>dep[v])swap(u,v);
	res+=SGT::query(1,1,n,num[u],num[v]);
	return res%mod;
}

更新子樹

我們在DFS的時候就已經發現:一顆子樹就是線段樹的一個區間
那麼更新子樹就只是對這個區間開刀的問題了

這個區間是:[num[u],num[u]+size[u])[num[u],num[u]+size[u])

inline void tree_update(int u,int k){
	k%=mod;
	SGT::update(1,1,n,num[u],num[u]+size[u]-1,k);
	return ;
}

inline int tree_query(int u){
	return SGT::query(1,1,n,num[u],num[u]+size[u]-1)%mod;
}

myACcode

#include<bits/stdc++.h>
#define re register
#define in Read()
#define int long long
//#define debug
int in{
	int i=0,f=1;char ch=getchar();
	while(!isdigit(ch)&&ch!='-')ch=getchar();
	if(ch=='-')ch=getchar(),f=-1;
	while(isdigit(ch))i=(i<<1)+(i<<3)+ch-48,ch=getchar();
	return i*f;
}

const int NNN=2e6+10;
int n,m,root,mod;
int w[NNN],wt[NNN];//w對應樹的初始權值,wt對應鏈的初始權值 

using std::vector;
vector<int>G[NNN];
using std::swap;

int father[NNN],son[NNN],dep[NNN],size[NNN];
int top[NNN],num[NNN],cnt;
int tree[NNN],lazy[NNN];

namespace SGT{
	
	const int NNN=::NNN<<2;//裏面的是NNN,外面的是::NNN,線段樹要開4倍 
	 
	inline void pushup(int p){tree[p]=(tree[p<<1]+tree[p<<1|1])%mod;}
	inline void pushdown(int p,int len/*=r-l+1,注意多個lazy要打*/){
		lazy[p<<1]+=lazy[p];
		lazy[p<<1|1]+=lazy[p];
		tree[p<<1]+=lazy[p]*(len-(len>>1));
		tree[p<<1|1]+=lazy[p]*(len>>1);
		tree[p<<1]%=mod;
		tree[p<<1|1]%=mod;
		lazy[p]=0;
	}
	
	inline void build(int p,int l,int r){
		if(l==r){
			tree[p]=wt[l];
			if(wt[l]>mod)tree[p]%=mod;
			return ;
		}
		int mid=(l+r)>>1;
		build(p<<1,l,mid);
		build(p<<1|1,mid+1,r);
		pushup(p);
	}
	
	inline void update(int p,int l,int r,int L,int R,int k){
		if(L<=l&&r<=R){
			tree[p]+=k*(r-l+1);
			lazy[p]+=k;
			return ;
		}
		
		int mid=(l+r)>>1;
		if(lazy[p]) pushdown(p,r-l+1);
		if(L<=mid)update(p<<1,l,mid,L,R,k);
		if(R>mid)update(p<<1|1,mid+1,r,L,R,k);
		pushup(p);
	}
	
	inline int query(int p,int l,int r,int L,int R){
		int res=0;
		if(L<=l&&r<=R){
			if(lazy[p])pushdown(p,r-l+1);
			res+=tree[p];
			res%=mod;
			return res;
		}
		
		int mid=(l+r)>>1;
		if(lazy[p])pushdown(p,r-l+1);
		if(L<=mid)res+=query(p<<1,l,mid,L,R);
		if(R>mid)res+=query(p<<1|1,mid+1,r,L,R);
		return res;
	}
}

inline void pre_DFS(int u,int fa,int depth){
	dep[u]=depth;
	father[u]=fa;
	size[u]=1;//一個葉子節點也是一棵子樹 
	int heavy_size=-1;
	for(int v:G[u]){
		if(!(v^fa))continue;
		pre_DFS(v,u,depth+1);
		size[u]+=size[v];
		if(size[v]>heavy_size)heavy_size=size[v],son[u]=v;
	}
}

inline void DFS(int u,int t/*top*/){
	num[u]=++cnt;
	top[u]=t;
	wt[cnt]=w[u];
	if(!son[u])return ;
	DFS(son[u],t);
	for(int v:G[u]){
		if(!(v^son[u])||!(v^father[u]))continue;
		DFS(v,v);
	}
}

inline void pre_work(){
	pre_DFS(root,0,1);
	DFS(root,root);
	SGT::build(1,1,n);
}

inline void chain_updating(int u,int v,int k){
	k%=mod;
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]])swap(u,v);
		SGT::update(1,1,n,num[top[u]],num[u],k);
		u=father[top[u]];
	}
	if(dep[u]>dep[v])swap(u,v);
	SGT::update(1,1,n,num[u],num[v],k);
	return ;
}

inline int chain_query(int u,int v){
	int res=0;
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]])swap(u,v);
		res+=SGT::query(1,1,n,num[top[u]],num[u]);
		res%=mod;
		u=father[top[u]];
	}
	if(dep[u]>dep[v])swap(u,v);
	res+=SGT::query(1,1,n,num[u],num[v]);
	return res%mod;
}

inline void tree_update(int u,int k){
	k%=mod;
	SGT::update(1,1,n,num[u],num[u]+size[u]-1,k);
	return ;
}

inline int tree_query(int u){
	return SGT::query(1,1,n,num[u],num[u]+size[u]-1)%mod;
}

signed main(){
	n=in,m=in,root=in,mod=in;
	for(re int i=1;i<=n;++i)w[i]=in;
	
	for(re int i=1;i<n;++i){
		int u=in,v=in;
		G[u].push_back(v);
		G[v].push_back(u);
	}
	
	pre_work();
	
	for(re int i=1;i<=m;++i){
		int Q=in;
		switch (Q){
			case 1:{
				int u=in,v=in,k=in;
				chain_updating(u,v,k);
				break;
			}
			case 2:{
				int u=in,v=in,res=0;
				res+=chain_query(u,v);
				printf("%lld\n",res);
				break;
			}
			case 3:{
				int u=in,k=in;
				tree_update(u,k);
				break;
			}
			case 4:{
				int u=in;
				printf("%lld\n",tree_query(u));
				break;
			}
		}
	}
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章