树剖 - 其实并没有那么难

树剖

将树剖分为链,然后我们就可以利用我们非常熟悉的线段树来做了

例题: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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章