树链剖分(轻重链剖分)

树链剖分是一种将树转化为线段树形式的技术,它将树分解为轻链和重链,允许在n*log2(n)的时间复杂度内维护树上路径信息,如路径修改和路径和查询。算法包括轻重儿子的定义、性质和时间复杂度证明,以及通过DFS计算节点信息和建立线段树。此外,树链剖分还可用于求解两点间的最近公共祖先(LCA)问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

树链剖分(轻重链剖分)


简介

树链剖分,简称”树剖“,顾名思义,就是将树“解剖”,将其转化成可用线段树维护的形式。由于线段树仅能进行区间上的操作,故需要通过一种划分方式,使得树链转化成区间,再用线段树维护。而“轻重儿子”的划分方式,不仅将树链转化成区间,还保证了该算法的复杂度。该算法可以用小常数n*log2 (n)的时间复杂度,维护一颗形态固定的树的树上路径信息,如对一段路径上的点进行修改,询问一段路径上所有点的权值和等等。值得一提的是,该算法还顺便可以用于求出两点之间的LCA.复杂度也为log,并且求LCA的过程比倍增法的常数小一些。
当然,如果条件允许,也可以用树状数组等数据结构替换线段树。


算法流程

1.划分方式
首先进行定义:
定义size[x]表示以x为根的子树的节点个数,也即一棵子树的大小;
定义v指x的子节点,fa[x] 指x的父节点;定义son[x] 为x的“重儿子”,满足son[x]为x的子节点并且size[son[x]]为size[v]中的最大值。如果存在多个子结点满足上述条件,则令son[x]为其中任意一个即可;定义轻儿子为除son[x]以外的 所有 的v;
定义重边为连接x与son[x]的边,重链为由重边组成的极长的路径;定义轻边为连接x与其轻儿子的边,轻链为树中由轻边组成的极长路径。特殊地,起点端点重合的路径算作特殊的重链。
定义 dfn[x] 表示x的时间戳;dep[x] 表示x的深度,设根节点深度为1;top[x] 表示x所在重链中 深度最小的点,也即树链顶端。特殊地,若x为轻儿子,则top[x]=x

可以发现,每一棵树中仅存在轻链和重链两种路径,这就是划分方式,接下来就要对这两种链分别进行维护

2.性质及时间复杂度证明
性质1:如果(u,v)为轻边,则必有size[v]<=size[x]/2
证明:反证法易证。
性质2:从根节点到某一点v的路径上的轻边个数不多于log(n)个
证明:由性质1,每一次经过一条轻边,子树大小最劣情况下减为原来的一半,故最多经过log(n)条轻边使得子树大小为1,也即到达叶子节点
性质3:每个到根节点得路径上都有不超过log(n)条轻链和不超过log(n)条重链
证明:由轻链和重链的定义可知,一段路径上一定是
轻链-重链-轻链-重链……交替的,而轻边个数不超过log(n)条,故轻重链均不超过log(n)条
时间复杂度证明:由于区间修改复杂度为log,最多进行log(n)次区间修改,故复杂度O(nlog2(n)).。在大多数情况下都跑不满,常数较小(据某位大佬说,nlog(n)的LCT跑不过树剖)

3.具体过程
1.通过一遍dfs,统计出dep[ ],size[ ]和son[ ]。
2.再用一边dfs,求出每个点的dfn序,并统计top[ ]。在这一次dfs中,优先遍历重儿子,通过这种方式确保同一条重链上的点的dfs序是连续的。
3.建立一颗dfs序线段树
4.对于修改一段路径(x,y)上的点权,将一条路径视为两条连向LCA的链,对每一段重链进行区间修改,在两条重链之间的轻边上通过fa[x]朴素地向父节点移动并进行单点修改,直至达到下一条重链的起点。更具体地,比较top[x]与top[y]的深度,若dep[top[x]]>dep[top[y]]则x=fa[top[x]],同时将x~top[x]之间的区间进行修改。当top[x]==x时区间退化为点,进行单点修改。
5.对于求两点x,y的LCA,相对更为简单,过程和4操作基本一致,不过不需要进行区间修改。最后top[x]==top[y]时,x与y在一条链上。此时x,y里面深度较小的点即为x,y的LCA
6.对于询问某一段区间的点权和,方法类似,只不过将上面的修改操作转换成询问操作
7.关于其他操作,均可以分为”向上跳“和”修改/查询“两种操作,应该差不多吧(反正我都不会就是了qwq)

code

#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define il inline
#define re register
il int read()
{
	int s=0,w=1;char c=getchar();
	while(c<'0'||c>'9'){ if(c=='-') w=-1;c=getchar();}
	while(c>='0'&&c<='9'){ s=(s<<1)+(s<<3)+c-'0';c=getchar();}
	return s*w;
}
const int N=1e5+10;
int n,m,rt,a[N];
LL P;
int head[N],ver[N<<1],nxt[N<<1],tot;
int fa[N],timer,dep[N],sz[N],son[N],dfn[N],top[N],rev[N];
struct TR{
	int l,r;
	LL sum,lz;
}tree[N<<2];
il void kkk(int u,int v)
{
	ver[++tot]=v;
	nxt[tot]=head[u];
	head[u]=tot;
}

il void pushup(int x)
{
	tree[x].sum=(tree[x<<1].sum+tree[x<<1|1].sum)%P;
}
il void pushdown(int x)
{
	tree[x<<1].sum=(tree[x<<1].sum+(tree[x<<1].r-tree[x<<1].l+1)*tree[x].lz%P)%P;
	tree[x<<1].lz=(tree[x<<1].lz+tree[x].lz)%P;
	
	tree[x<<1|1].sum=(tree[x<<1|1].sum+(tree[x<<1|1].r-tree[x<<1|1].l+1)*tree[x].lz%P)%P;
	tree[x<<1|1].lz=(tree[x<<1|1].lz+tree[x].lz)%P;
	
	tree[x].lz=0;return;
}
il void maketree(int x,int l,int r)
{
	tree[x].l=l,tree[x].r=r,tree[x].lz=tree[x].sum=0;
	if(l==r){
		tree[x].sum=a[rev[l]];return;
	}
	int mid=(l+r)>>1;
	maketree(x<<1,l,mid);maketree(x<<1|1,mid+1,r);
	pushup(x);
}
il void upd(int x,int l,int r,int y)
{
	//printf("%d -- %d: [%d,%d]\n",l,r,tree[x].l,tree[x].r);
	if(l<=tree[x].l&&tree[x].r<=r){
		LL summ=(tree[x].r-tree[x].l+1)*y%P;
		tree[x].sum=(tree[x].sum+summ)%P,tree[x].lz=(tree[x].lz+y)%P;
		return;
	}
	if(tree[x].lz!=0) pushdown(x);
	if(tree[x<<1].r>=l) upd(x<<1,l,r,y);
	if(tree[x<<1|1].l<=r) upd(x<<1|1,l,r,y);
	pushup(x);return;
}
il LL que(int x,int l,int r)
{
	if(l<=tree[x].l&&tree[x].r<=r) return tree[x].sum;
	if(tree[x].lz!=0) pushdown(x);
	LL summ=0;
	if(tree[x<<1].r>=l) summ=que(x<<1,l,r);
	if(tree[x<<1|1].l<=r) summ=(summ+que(x<<1|1,l,r))%P;
	return summ; 
}

il void dfs1(int x,int FA)
{
	dep[x]=dep[FA]+1;
	fa[x]=FA;
	for(re int i=head[x];i;i=nxt[i]){
		if(ver[i]==FA) continue;
		dfs1(ver[i],x);
		sz[x]+=sz[ver[i]];
		if(son[x]==0 || sz[son[x]]<sz[ver[i]]) son[x]=ver[i];
	}
	sz[x]++;return;
}
il void dfs2(int x,int T)
{
	dfn[x]=++timer;
	rev[dfn[x]]=x;
	top[x]=T;
	if(son[x]) dfs2(son[x],T);
	for(re int i=head[x];i;i=nxt[i]){
		if(ver[i]==fa[x] || ver[i]==son[x]) continue;
		dfs2(ver[i],ver[i]);
	}
}
il int lca(int u,int v)
{
	int tu=top[u],tv=top[v];
	while(tu!=tv){
		if(dep[tu]<dep[tv]) swap(tu,tv),swap(v,u);
		u=fa[tu],tu=top[u];
	}
	if(dep[u]<dep[v]) return u;
	return v;
}
il void modify(int u,int v,int w)
{
	int tu=top[u],tv=top[v];
	while(tu!=tv){
		if(dep[tu]<dep[tv]) swap(tu,tv),swap(u,v);
		upd(1,dfn[tu],dfn[u],w);
		u=fa[tu],tu=top[u];
	}
	if(dep[u]>dep[v]) swap(u,v);
	upd(1,dfn[u],dfn[v],w);
	return;
}
il LL getsum(int u,int v)
{
	int tu=top[u],tv=top[v];
	LL res=0;
	while(tu!=tv){
		if(dep[tu]<dep[tv]) swap(tu,tv),swap(u,v);
		res=(res+que(1,dfn[tu],dfn[u]))%P;
		u=fa[tu],tu=top[u];
	}
	if(dep[u]>dep[v]) swap(u,v);
	res=(res+que(1,dfn[u],dfn[v]))%P;
	return res%P;
}
int main()
{
	n=read(),m=read(),rt=read(),P=read();
	for(re int i=1;i<=n;i++) a[i]=read();
	for(re int i=1;i<n;i++){
		int uu=read(),vv=read();
		kkk(uu,vv);kkk(vv,uu);
	}
	dfs1(rt,0);
	dfs2(rt,rt);
	maketree(1,1,n);
	for(re int i=1;i<=m;i++){
		int op=read();
		if(op==1){
			int uu=read(),vv=read(),ww=read()%P;
			modify(uu,vv,ww);
		}
		if(op==2){
			int uu=read(),vv=read();
			printf("%lld\n",(getsum(uu,vv)%P+P)%P);
		}
		if(op==3){
			int xx=read(),yy=read()%P;
			upd(1,dfn[xx],dfn[xx]+sz[xx]-1,yy);
		}
		if(op==4){
			int xx=read();
			printf("%lld\n",(que(1,dfn[xx],dfn[xx]+sz[xx]-1)%P+P )%P);
		}
	}
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值