浅谈树链剖分

本文深入探讨了树链剖分技术,一种高效处理树状结构数据的算法,特别适用于解决链加子树求和问题。文章详细解释了重边、重儿子、重链、轻边的概念,以及如何通过双DFS实现树链剖分,配合线段树进行快速查询。通过实例代码展示了树链剖分的查询过程,包括如何利用线段树维护重链信息,以及如何通过跳重链顶端的方式查找LCA。

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

是谁给我的勇气,让我在一道树链剖分都没写的情况下就来写博客?

当然是xxx啦

进入正题。

按照我的理解,我们可以用dfs序处理很多问题,各种差分各种求和都可以,但是当我们遇到链加子树求和这种情况时,你会发现我们好像没有了时间复杂度优秀的手段。

于是大佬们想出了树链剖分。

定义一下:

重边是一个结点的儿子中size(子树大小)最大的那个点与自己相连的边。

重儿子就是一个节点最疼爱的儿子用重边连接起来的儿子。

重链自然就是一串重边连起来的东西。

轻边就是不是重边的边(好扯的定义)。没有轻链这一说ovo。

树链剖分的意义在于,它通过将原树切成一条条的不相交的重链来方便维护。比如说用线段树什么的。重链和重链间用轻边连接(废话除了重边就只有轻边)。

于是在询问的时候,可以通过查询一条重链里的信息,然后遇到轻边就暴力跳过去就行。(大佬眼里的暴力qwq)

我把树链剖分的实现称之为DD,即Double Dfs。

第一遍dfs:算出所有点的父亲fa,子树大小size,深度dep,以及所有儿子中结点最多的那个儿子son,都记录下来。

第二遍dfs:算出所有点所在的重链的顶端top(利用第一遍算出来的son和size),以及所有点的dfs序。

这里要注意在第二遍算dfs序的时候,要优先走重儿子。为什么呢!我认为这是最重要的:因为先走重儿子你的重链上点的dfs序才是连续的,这样才能用线段树维护,要不然序号不一样线段树也要泪奔。代码如下:

void dfs(int x){
	size[x]=1;
	for (int i=first[x],y;i;i=nxt[i])
		if ((y=to[i])!=fa[x]){
			fa[y]=x;
			dep[y]=dep[x]+1;
			dfs(y);
			size[x]+=size[y];
			if (size[y]>size[son[x]]) son[x]=y;//确定重儿子 
		}
}
void dfs(int x,int Anc){//当前重链的顶端为Anc 
	id[x]=++tot;
	top[x]=Anc;
	if (son[x]) dfs(son[x],Anc);//优先处理重儿子 
	for (int i=first[x],y;i;i=nxt[i])
		if ((y=to[i])!=fa[x]&&y!=son[x]) dfs(y,y);//如果是轻边,那就意味着新开了一条重链 
}

那树链剖分到底怎么查询信息呢QAQ?

由于之前我们已经令一条重链上的结点序号连续,那么剩下的就是线段树了。

这里的查询方式很简单:

1.给出要查询的链的两个端点x,y,注意这条链可能横跨多条重链。

2.令a,b分别为x,y的top(重链顶端)。

3.把深的先查询,然后往上跳(像LCA那样)。

4.判断跳完后的新top a,b是否相等。如果还不相等,就回到第二步继续跳,反之进入第五步。

5.还要查一次,也就是最后的一条链。

换句话说,两个端点同时出发,你跳一下,我跳一下,最后撞到一起了就停下。

事实上,树链剖分求LCA是非常简单的。只要两个点跳到top相等了,深度较浅(也就是较高的那个)就是LCA。

代码实现如下:

int calc(int x,int y){
	int a=top[x],b=top[y];//ab是top,xy是端点 
	int res=0;//这是你要查询的信息变量,,因题而异,这里默认为求链长 
	while (a!=b){
		if (dep[a]>dep[b]) swap(a,b),swap(x,y);//保证b是深的那个。 
		Ql=id[b],Qr=id[y];//该重链的两个端点 
		res+=Query(1,1,n);//因为dfs序连续,所以直接查询。QL和QR是全局变量 
		y=fa[b],b=top[y];//将深的那个top变成父亲(上面那条重链的尾巴),而原来的b又变成了上面那条重链的top。 
	}
	Ql=id[x],Qr=id[y];//最后处理(还剩一小块) 
	if (Ql>Qr) swap(Ql,Qr);
	res+=Query(1,1,n);
	return res;
}

树链剖分和线段树,树状数组,splay等数据结构是如影随形的,像我这种线段树都不会写的菜菜可以自闭了QAQ

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值