Link-Cut-Tree 树上操作

L C T \tt LCT LCT,即 Link \texttt{Link} Link- Cut \texttt{Cut} Cut- Tree \texttt{Tree} Tree,是一种维护森林的数据结构。

比树链剖分支持更多操作,比如说树上翻转权值。

前置知识

S p l a y \tt Splay Splay 和其文艺平衡树用法

算法用途

维护森林,以更大的常数和复杂度支持更多的操作。

算法复杂度

时间

O ( n log ⁡ n + m log ⁡ n ) O(n \log n + m \log n) O(nlogn+mlogn)

空间

O ( n ) O(n) O(n)

算法实现

实链剖分

实链剖分将一棵树分成实边和虚边,实链剖分后的一棵树满足如下要求:

  1. 树中一条边要么是实边要么是虚边。
  2. 树中每个节点只能连一条实边向其中一个儿子,连向其他儿子的都是虚边。

虚实是可以变化的,并不是固定的,在需要时可以将虚边改成实边,将实边改成虚边。

实链:相连的实边所组成的链
实儿子:这个节点在其所在的实链上的儿子。

一颗被实链剖分过的树

LCT

L C T \tt LCT LCT 将森林中每棵树实链剖分一遍,虚边表示为父亲不认儿子,儿子认父亲,就是 father[v] = u, son[u] != v,实边则是 father[v] = u, son[u] = v(因为每个节点只有一个实边连向儿子,所以可以当成每个节点只指向一个孩子和一个父亲)。

然后,每条实链用一个 S p l a y \tt Splay Splay 来维护, S p l a y \tt Splay Splay 中序遍历时的节点顺序按照深度严格递增(可以理解为 S p l a y \tt Splay Splay 按照深度当作关键字)。为了方便,我们以后用一棵树来演示,而不是整个森林。

左图是原树上显示虚边实边,右图是LCT,框起来代表一个Splay里面的,框中按照Splay来显示

ACCESS

L C T \tt LCT LCT 最重要也是最难的操作是打通 a c c e s s ( x ) \tt access(x) access(x)

这个操作是为了打通一个从 x x x 到根节点的路径,使其变成一条实链。例如 a c c e s s ( L ) \tt access(L) access(L)

access(L)的变化(按原树显示)
a c c e s s ( a ) \tt access(a) access(a) 的步骤如下(初始 x = a , y = 0 \tt x = a, y = 0 x=a,y=0

  1. s p l a y ( x ) \tt splay(x) splay(x),将 x \tt x x 伸展到其所在 s p l a y \tt splay splay 的根。
  2. x \tt x x 的右儿子换成 y \tt y y,想想为什么是右儿子?因为 S p l a y \tt Splay Splay 的关键字是深度,所以 x 的右儿子就是原树中 x 的实儿子,将实儿子换成 y y y
  3. y = x , x = f a t h e r [ x ] \tt y = x, x = father[x] y=x,x=father[x]

那么具体操作是什么呢?我们来看一下:

首先,将 L \tt L L 伸展至根

splay(L)
L \tt L L 的在 S p l a y \tt Splay Splay 中的右儿子换成 0 0 0,因为我们要打通 L L L A A A,那么就需要将 L \tt L L 以下的那些节点割掉。


S p l a y ( G ) \tt Splay(G) Splay(G),(既然没改变就直接与下一个操作合在一起讲了),将 G G G 右儿子换成 L L L

在这里插入图片描述
直接将接下来两个展示了吧。

在这里插入图片描述

打通后的实链按照深度排序是 A C G J L \tt ACGJL ACGJL S p l a y \tt Splay Splay 的中序遍历是 A C G J L \tt ACGJL ACGJL,没问题。

L C T \tt LCT LCT 最重要,最难的操作讲完了,后面就跟割草一样简单了。

MAKEROOT

m a k e r o o t ( y ) \tt makeroot(y) makeroot(y) y \tt y y 变成其所在树的根节点。

我们假设 y \tt y y 为要换成的根, x \tt x x 为原来的根。

那么整棵树可以分成 4 4 4 个部分:

  1. x \tt x x 的儿子中不包含 y \tt y y 的那些子树的节点(红)
  2. y \tt y y 的子树(不包括 y \tt y y,蓝)
  3. x \tt x x y \tt y y 的路径上的节点(包括 x , y \tt x,y x,y,紫和黑)
  4. 其他的节点(绿)

在这里插入图片描述
可见当根从 x \tt x x 转变到 y \tt y y 的时候,红色部分的父亲是不会改变的,蓝色和绿色也一样,只有紫色部分的会倒过来。

所以就可以这样更换根节点:

access(y), splay(y), reverse(y)

打通一条到原根节点的通道,伸展 y \tt y y,翻转 y \tt y y 子树中的节点

最后一步至关重要,我们想想: S p l a y \tt Splay Splay 中的节点按照深度排序,所以翻转后,这条 x x x y y y 实链中的节点,就会深度颠倒,就实现了换根。

FINDROOT

f i n d r o o t ( x ) \tt findroot(x) findroot(x) 查找 x \tt x x 在原树中的根,多用于判断连通性。

access(x), splay(x) 然后一直找 x \tt x x 的左儿子,最左边的就是根了。

SPLIT

s p l i t ( x , y ) \tt split(x,y) split(x,y) x \tt x x y \tt y y 的路径装进一个 s p l a y \tt splay splay 里,这个很容易实现。

先将 x \tt x x 变成根,然后 a c c e s s ( y ) \tt access(y) access(y) 就行了。

LINK

l i n k ( x , y ) \tt link(x, y) link(x,y) 连一条 x , y \tt x, y x,y 之间的轻边,让 x x x 成为 y y y 的父亲。

makeroot(y), father[y] = x

CUT

c u t ( x , y ) \tt cut(x, y) cut(x,y),将 x , y \tt x,y x,y 之间的边断开。

断开简单啊:split(x,y), father[y] = c[x][0]=0;

如果不存在这条边呢?我们要如何判断这条边存不存在?

makeroot(x)

如果这条边存在,则一定满足下面的要求:

  • f a t h e r [ y ] = x \tt father[y] = x father[y]=x
  • f i n d r o o t ( y ) = x \tt findroot(y) = x findroot(y)=x
  • c h i l d [ y ] [ 0 ] ! = 0 \tt child[y][0] != 0 child[y][0]!=0

如果全部满足,就代表这条边存在。就断开

L C T \tt LCT LCT 讲完了,不想放代码了。我学了 2 \tt 2 2 天才彻底理解 L C T \tt LCT LCT

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值