树相关——树链剖分(轻重链剖分)

该博客介绍了树链剖分的概念及其在在线最短路径问题中的应用。通过DFS预处理获取节点信息,然后利用树链剖分保证重链的连续性,结合线段树实现对树上两点间路径上节点值的在线修改和查询。文章详细阐述了重儿子、轻儿子、重链和轻链的定义,并提供了相应的代码实现。
摘要由CSDN通过智能技术生成

先把线段树学了再来看这篇博客

树链剖分引入

 我们来看一个例子:
 在树上的两个点的最短路径上的所有点的值加上 c c c。对于每次询问,求出树上任意两点的最短距离。

 如果这是一个离线的问题,直接就用树上差分+ d f s dfs dfs即可解决。但是如果这个问题是在线的,我们就不得不需要一种在线的数据结构来解决这个问题。于是,在这里我们引入树链剖分。

树链剖分基本概念

基本概念:
1.重儿子:假设 x x x n n n个儿子节点,在 n n n个节点中以 y y y儿子节点的为根子树大小最大,那么 y y y就是 x x x的重儿子
2.轻儿子:除重儿子外的所有儿子均为轻儿子
3.轻边: x x x与轻儿子相连的边
4.重边: x x x与重儿子相连的边
5.轻链:均由轻儿子构成的一条链(一条轻链不一定只有一个节点)
6.重链:均由重儿子构成的一条链

我们以下图来举一个例子:
在这里插入图片描述
 如上图所示, 3 3 3就是 1 1 1的重儿子, 5 5 5 2 2 2的重儿子, 1 − > 3 − > 6 − > 9 − > 10 1->3->6->9->10 1>3>6>9>10 2 − > 5 − > 8 2->5->8 2>5>8就是一条重链。(1,3)就是一条重边。(3,7)就是一条轻边。

第一部分——预处理部分

我们需要的信息如下
d e p [ x ] dep[x] dep[x] x x x节点的深度
f a [ x ] fa[x] fa[x] x x x节点的父亲节点
s o n [ x ] son[x] son[x] x x x节点的重儿子
s i z [ x ] siz[x] siz[x] x x x节点为根的子树大小
t o p [ x ] top[x] top[x] x x x节点所在链的顶点

 如何得到这些信息呢? d f s dfs dfs可以考虑一下。

 我们用一个 d f s dfs dfs维护前四个信息。代码入下。

void dfs1(int u,int father){
	dep[u]=dep[father]+1,fa[u]=father,siz[u]=1;
	for(int e=first[u];e;e=nex[e]){
		int v=to[e];
		if(v==fa[u])continue;
		dfs1(v,u);
		siz[u]+=siz[v];
		if(siz[v]>siz[son[u]])son[u]=v;
	}
}

 接下来获取 t o p [ x ] top[x] top[x],我们处理的方式:优先对重儿子处理,重儿子处理结束后再处理轻儿子(新开链。
 浅浅地讲一下为什么要先搜重儿子。因为我们要维护的是重链,而一条链的要求必须是连续的,而我们维护时使用数据结构,必然是要将它转换到数列上来做的,如何转换呢?最好的方法就是按照 d f s dfs dfs 序,此时如果不先搜重儿子的话,重链上的 d f s dfs dfs序就可能会断掉,如下图。
在这里插入图片描述

void dfs2(int u,int f){
	top[u]=f;dfn[u]=++cnt;nv[cnt]=a[u];//nv[]为接下来的线段树准备
	if(son[u]) dfs2(son[u],f);
	for(int e=first[u];e;e=nex[e]){
		int v=to[e];
		if(v==fa[u] or v==son[u])continue;
		dfs2(v,v);
	}
}

线段树部分

不要告诉我你不会线段树?

修改 and 查询

 因为要维护的是链,而且我们现在已经保证链上的 d f s dfs dfs序连续了,所以我们直接取结点 x x x t o p [ x ] top[x] top[x]到它自己这一段进行修改或查询(即使用 d f s dfs dfs序修改),然后再将当前结点跳到它的 f a [ t o p [ x ] ] fa[top[x]] fa[top[x]]即可。为了防止一个结点无限往上跳,我们每次都选 t o p top top值比较深的那个结点进行修改/查询,再往上跳,就可以防止无限跳的情况了。
 而如果选的是浅的,而它又往上跳,则深度越来越浅,必然会无限跳,最终死循环。
 最后,这两个结点一定会到一条链上,而且必然有一个点会是 L C A LCA LCA,我们最后进行一次操作即可。

void addPath(int x,int y,int k){
	k%=P;int fx=top[x],fy=top[y];
	while(fx!=fy)
		if(dep[fy]>dep[fx]) swap(fy, fx),swap(y,x);
		else{
			update(1,dfn[fx],dfn[x],k);
			x=fa[fx],fx=top[x];
		}
	if(dfn[x]<=dfn[y])update(1,dfn[x],dfn[y],k);
	else update(1,dfn[y],dfn[x],k);
}

int findPath(int x,int y){
	int ans=0,fx=top[x],fy=top[y];
	while(fx!=fy)
		if(dep[fx]<dep[fy])swap(fx,fy),swap(x,y);
		else{
			ans=(ans+query(1,dfn[fx],dfn[x]))%P;
			x=fa[fx],fx=top[x];
		}
	if(dfn[x]<=dfn[y])ans=(ans+query(1,dfn[x],dfn[y]))%P;
	else ans=(ans+query(1,dfn[y],dfn[x]))%P;
	return ans%P;
}

查询子树 and 修改子树如下:

inline void AddSubtree(int x,int k){update(1,dfn[x],dfn[x]+siz[x]-1,k);}

inline int FindSubtree(int x){return query(1,dfn[x],dfn[x]+siz[x]-1)%P;}

完整代码

#include<bits/stdc++.h>
#define in read()
#define MAXN 100050
#define MAXM MAXN<<2
#define re register
#define ls p<<1
#define rs p<<1|1
#define int long long
using namespace std;

struct SGT{
	int l,r,sum,sumtag;
}t[MAXM];

int n,m,rt,P,a[MAXN];
int nex[MAXM],first[MAXN],to[MAXM],tot=0;
int dep[MAXN],fa[MAXN],siz[MAXN],top[MAXN],son[MAXN],dfn[MAXN],nv[MAXN],cnt=0;

inline int read(){
	int x=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)){x=(x<<3)+(x<<1)+c-'0';c=getchar();}
	return x*f;
}
 
inline void addedge(int u,int v){
	nex[++tot]=first[u];
	first[u]=tot;
	to[tot]=v;
}

inline void pushup(int p){t[p].sum=t[ls].sum+t[rs].sum;}

inline void pushdown(int p){
	if(t[p].sumtag){
		t[ls].sum=(t[ls].sum%P+t[p].sumtag*(t[ls].r-t[ls].l+1)%P)%P;
		t[rs].sum=(t[rs].sum%P+t[p].sumtag*(t[rs].r-t[rs].l+1)%P)%P;
		t[ls].sumtag=(t[ls].sumtag%P+t[p].sumtag%P)%P;
		t[rs].sumtag=(t[rs].sumtag%P+t[p].sumtag%P)%P;
		t[p].sumtag=0;
	}
}

void build(int p,int l,int r){
	t[p].l=l,t[p].r=r;
	if(l==r){t[p].sum=nv[l];return;}
	int mid=(l+r)>>1;
	build(ls,l,mid),build(rs,mid+1,r);
	pushup(p);
}

void update(int p,int l,int r,int v){
	if(l<=t[p].l and t[p].r<=r){t[p].sum+=v*(t[p].r-t[p].l+1);t[p].sumtag+=v;return;}
	pushdown(p);
	int mid=(t[p].l+t[p].r)>>1;
	if(l<=mid)update(ls,l,r,v);
	if(r>mid) update(rs,l,r,v);
	pushup(p);
}

int query(int p,int l,int r){
	if(l<=t[p].l and t[p].r<=r){return t[p].sum;}
	pushdown(p);
	int ans=0,mid=(t[p].l+t[p].r)>>1;
	if(l<=mid)ans=(ans+query(ls,l,r))%P;
	if(r>mid) ans=(ans+query(rs,l,r))%P;
	return ans%P;
}

void dfs1(int u,int father){
	dep[u]=dep[father]+1,fa[u]=father,siz[u]=1;
	for(int e=first[u];e;e=nex[e]){
		int v=to[e];
		if(v==fa[u])continue;
		dfs1(v,u);
		siz[u]+=siz[v];
		if(siz[v]>siz[son[u]])son[u]=v;
	}
}

void dfs2(int u,int f){
	top[u]=f;dfn[u]=++cnt;nv[cnt]=a[u];
	if(son[u]) dfs2(son[u],f);
	for(int e=first[u];e;e=nex[e]){
		int v=to[e];
		if(v==fa[u] or v==son[u])continue;
		dfs2(v,v);
	}
}

void addPath(int x,int y,int k){
	k%=P;int fx=top[x],fy=top[y];
	while(fx!=fy)
		if(dep[fy]>dep[fx]) swap(fy, fx),swap(y,x);
		else{
			update(1,dfn[fx],dfn[x],k);
			x=fa[fx],fx=top[x];
		}
	if(dfn[x]<=dfn[y])update(1,dfn[x],dfn[y],k);
	else update(1,dfn[y],dfn[x],k);
}

int findPath(int x,int y){
	int ans=0,fx=top[x],fy=top[y];
	while(fx!=fy)
		if(dep[fx]<dep[fy])swap(fx,fy),swap(x,y);
		else{
			ans=(ans+query(1,dfn[fx],dfn[x]))%P;
			x=fa[fx],fx=top[x];
		}
	if(dfn[x]<=dfn[y])ans=(ans+query(1,dfn[x],dfn[y]))%P;
	else ans=(ans+query(1,dfn[y],dfn[x]))%P;
	return ans%P;
}

inline void AddSubtree(int x,int k){update(1,dfn[x],dfn[x]+siz[x]-1,k);}

inline int FindSubtree(int x){return query(1,dfn[x],dfn[x]+siz[x]-1)%P;}

signed main(){
	n=in,m=in,rt=in,P=in;
	for(re int i=1;i<=n;i++)a[i]=in;
	for(re int i=1;i<=n-1;i++){
		int u=in,v=in;
		addedge(u,v),addedge(v,u);
	}
	dfs1(rt,0);
	dfs2(rt,rt);
	build(1,1,n);
	for(re int i=1;i<=m;i++){
		int opt=in;
		if(opt==1){
			int x=in,y=in,z=in;
			addPath(x,y,z);
		}
		if(opt==2){
			int x=in,y=in;
			cout<<findPath(x,y)%P<<'\n';
		}
		if(opt==3){
			int x=in,z=in;
			AddSubtree(x,z);
		} 
		if(opt==4){
			int x=in;
			cout<<FindSubtree(x)%P<<'\n';
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值