LCT 详解

本文详细介绍了Link-Cut Tree(LCT)这一高级数据结构,它结合了Splay Tree的特点来维护树剖。LCT通过access、evert、findrt、link和cut等操作实现树的动态变化。文章通过实例解释了LCT如何维护子树信息,并提供了基础和复杂情况下的代码示例。

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

今天学了个叫 “LCT” 的东西,很多人估计越听越懵。

Link-Cut Tree 确实是个很不好理解的东西,就连我也是。

就算是老师讲也感觉很模糊。


首先,这个 LCT 也可以理解为“用 Splay 来维护树剖”,这里的树剖指的是实链剖分(专门为 LCT 服务的剖分方式)。

一棵树中,处于同一条实链的节点用同一个 Splay 维护。由于 Splay 灵活多变,完全可以代替线段树。

然后,每一个 Splay 内维护的节点键值是他们在原来树中的深度。即对于一个 Splay 节点键值 xxx,左子树的所有节点在树中的深度都小于 xxx,右子树的所有节点在树中的深度都大于 xxx

于是,代码的前半部分完全用来写 Splay。

注意,有一个重点就是,树中的实链(即一棵 Splay)中的节点连的是双向边,而非实边连的是单向边(儿子认父,父不认儿子),这样有助于判断一个节点是否是 Splay 的根。

但是,之后呢?我们定义一个函数 access(x)access(x)access(x) 表示,现在把 root,...,xroot,...,xroot,...,x 这一条链搞成一条实链,并且 xxx 下面不接任何实边(注意 root,...,xroot,...,xroot,...,x 这些节点原本接的实边要删除)

如上图,红色的为实边(有些节点也可以不接实边,比如 777)。

接下来执行 access(5)access(5)access(5)

执行 access(8)access(8)access(8)

可以发现,每做一次 access(x)access(x)access(x),其实就是把 root,...,xroot,...,xroot,...,x 重构成一条实链,并单独用一个 Splay。

accessaccessaccess 函数是 LCT 中最重要的部分,也是 LCT 中唯一连接实线的方式。

void access(int x) //x 不为空,直到根为止
{
   
   
	int y=0;
	while(x)
	{
   
   
		splay(x);
		a[x].c[1]=y; //节点 x 原来接的实线清空,重新接到下面的节点,原来接的节点必然深度更深,即在平衡树右边
		a[y].fa=x;
		pushup(x);
		y=x;
		x=a[x].fa; //下一次更新实线用到
	}
}

函数 evert(x)evert(x)evert(x) :把 xxx 作为 xxx 所在树的与根。

考虑用 access(x)access(x)access(x)xxxrootrootroot 连接,然后伸展到平衡树的根,最后利用翻转来保证所在平衡树的深度。

比如执行 evert(8)evert(8)evert(8),连接了 1,7,81,7,81,7,8,以深度为键值建出平衡树:

888 节点伸展到根:

你会发现,如果把 888 作为根,有且只有这棵平衡树的深度需要重构,只需要对整棵树进行翻转即可。

翻转必然要打标记,必然要下放标记。因此在每次 Splay 伸展时,必须先从根传标记。

void evert(int x)
{
   
   
	access(x);
	splay(x);
	a[x].rev^=1; //翻转标记
}

函数 findrt(x)findrt(x)findrt(x) 查找 xxx 所在的树的根。

显然,先用 accessaccessaccess 连接 rootrootrootxxx,然后在平衡树中找深度最小的,即一直往左走。

int findrt(int x)
{
   
   
	access(x); //连接 root,x
	splay(x); //把 x 伸展到平衡树的根,也可以理解为找平衡树的根的一种方式
	while(a[x].c[0]) //一直往左走
	{
   
   
		x=a[x].c[0];
	}
	return x;
}

函数 link(x,y)link(x,y)link(x,y),连接原来树上 (x,y)(x,y)(x,y) 两个节点。

类似于并查集的合并,我们可以先把 xxx 作为树根 evert(x)evert(x)evert(x)

然后把根的父亲接到 yyy 后面。注意此时 x,yx,yx,y 不在同一个 Splay。

void link(int x,int y)
{
   
   
	if(findrt(x)==
	findrt(y))return; //判断根是否相同
	evert(x);
	a[x].fa=y;
}

函数 cut(x,y)cut(x,y)cut(x,y) 表示切断原来书上 (x,y)(x,y)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值