[复习资料]关于LCT
关于LCT
实链剖分与LCT基础
树上信息不好维护,可以先断掉树上的若干条边,使得树退化为若干条链,然后依次维护这些链。
对于一棵有根树而言,可以选择让保留下来的若干条链上的点之间都是祖先关系,把断掉的边认为是虚边,保留下来的边认为是实边,这样我们得到的就是一棵树的实链剖分(的一种方案),不难发现一个点的所有孩子边中最多只有一条实边。
LCT 的本质就是维护实链剖分,用 splay 来维护每条实链的信息, splay 与 splay 之间通过虚边联系起来,一个 splay 的中序遍历得到的是其对应实链按深度从浅到深排列得到的序列, splay 的根节点的父亲节点表示的是这条实链中深度最浅的点的父亲。( LCT 的基本信息)
把某个节点 \(x\) 旋转到它所在的 splay 的根,那么 \(x\) 的左儿子子树表示的就是 \(x\) 所在实链中深度比 \(x\) 浅的节点,右儿子子树表示的就是 \(x\) 所在实链中深度比 \(x\) 深的节点,此时可以发现, \(x\) 的实儿子虚儿子切换在 LCT 中对应的就是切换 \(x\) 的右儿子子树。( LCT 虚实儿子/边切换)
利用实儿子虚儿子切换的技巧,可以很简单将一个节点 \(x\) 换到和当且整棵树的根同一个 splay 中,并且与此同时还可以让 \(x\) 成为 splay 中深度最深的节点,这时如果翻转 \(x\) 和根所在的这个 splay ,就会让 \(x\) 变成深度最浅的点,此时 \(x\) 就变成了整棵树的根。( LCT 换根操作)
利用 LCT 虚实儿子切换及换根操作,可以将树上任意一条路径 \(u-v\) 提取出来并全部放到一个 splay 中,此时就很好维护链上信息了。( LCT 维护链上信息)
LCT维护子树信息与动态dp
先仅考虑 LCT 求子树信息,对于某个节点的子树,子树信息可以分为两部分,一部分是实儿子所在的子树,另一部分是所有虚儿子所在的子树,可以先维护一波所有虚儿子所在子树的信息,然后利用 splay 将这条实链中所有虚子树的信息合并得到整棵子树的信息。( LCT 子树查询)
再考虑 LCT 修改子树信息,和上面差不多,关键是下传标记,一个点下传标记的时候并不能直接枚举它的所有虚儿子,否则时间复杂度就假掉了,可以再用一个 splay 来给所有虚儿子打标记,这样让下传标记的复杂度正确。( LCT 子树修改)
如果一个 dp 方程可以表示为矩阵相乘的形式,那么这个 dp 方程就可以用 splay 去维护,于是乎就可以用 LCT 去维护一个树上面的 dp 方程,也就是用 LCT 维护动态 dp ,这和 LCT 子树查询本质是一样的。( LCT 维护动态 dp )
LCT静态化(全局平衡二叉树)
有的时候我们并不需要实现 LCT 里面的大部分操作(换根、 Link 、 Cut 等),只需要链上查询和链上修改,那么此时我们就可以将 LCT 静态下来( splay 不需要旋转,所以我们可以一开始就找到 splay 最平衡的位置),具体实现方法就是用轻重链剖分的方法去虚实链剖分,然后对于每条实链,将每个点的点权认为是它所有虚儿子子树和,然后找带权重心一路递归下去得到这条实链的 splay ,这样得到的 LCT 满足每个点向上跳的次数是 \(\log_2n\) 级别的,因为虚边是 \(\log_2n\) 级别的,而每次跳实边(跳 splay 上面的父亲节点)包括的节点数量都会乘以 \(2\) ,所以每个点向上跳的次数是 \(\log_2n\) 级别的。
经过了静态化 LCT 的操作之后,维护链上信息和普通 LCT 的方法就有所不同了,此时如果将 splay 类似成为线段树的话那么维护链上信息的方法就和轻重链剖分维护的方法差不多,子树信息查询和普通 LCT 类似,子树信息修改此时每个点的虚儿子维护的 splay 也让其静态化,其它都是一样的。
LCT维护边信息
LCT 维护边信息可以将边转化成点,然后按维护点信息的方法去维护,也有另一种方法,就是将边信息下放到点信息上面去然后再维护。
对于某一个点 \(x\) ,它左儿子 \(l\) 维护 \(x\) 与 \(l\) 子树代表的点形成的链之间的边的信息,它右儿子 \(r\) 维护 \(x\) 与 \(r\) 子树代表的点形成的链之间的边的信息,这样的话于普通 LCT 而言几乎唯一需要更改的就是 rotate 操作了,分当且 rotate 的节点是否有另一边的儿子讨论即可。
这种做法的好处就是不需要为边重新开点了,降低了常数并减小了点和边不同处理的分类讨论。