树链剖分(轻重链剖分)

树链剖分(轻重链剖分)


简介

树链剖分,简称”树剖“,顾名思义,就是将树“解剖”,将其转化成可用线段树维护的形式。由于线段树仅能进行区间上的操作,故需要通过一种划分方式,使得树链转化成区间,再用线段树维护。而“轻重儿子”的划分方式,不仅将树链转化成区间,还保证了该算法的复杂度。该算法可以用小常数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
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值