LCT
前言
LCT用来维护的是森林。它可以支持加边(link)、删边(cut)、链信息修改和查询、子树信息查询。
LCT长啥样
首先我们对这些树进行实链剖分。我们对树中的每一条实链都用一棵splay维护。splay维护的是实链上的点按照深度从小到大排序后得到的序列。一个点在splay上的前驱和后继是它在实链和它相邻的点。splay中每个点的编号和这个点在树中的编号是相同的。特别的,一条实链的splay的根节点的父亲,是成这条实链的最浅点在树上的父亲。
基本操作
isroot(x)
这个函数用来判断x这个点是不是它所属的splay的根。
1 | bool isroot(int x) { return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x; } |
splay(x)
我们用这个函数把x转到x所属的splay的根节点。
注意rotate里面关于x的祖先的判断,要调用isroot(f),并且要在修改f的父亲之前。
1 | void rotate(int x) { |
要注意,如果x自己就是根节点,那么将不会执行到rotate语句,如果把push_down写在rotate里面,标记的下放就会有一些问题。
access(x)
利用这个函数我们让x和这棵树的根在同一条实链上,并且恰好是实链的两端。
首先我们把x转到它自己所属的splay的根部,此时它的右儿子部分是它在实链中比它深的点,应该和它断开。考虑x上方的实链,我们应该对fa[x]进行类似的操作(splay到根然后把右儿子和它断开),并且把x接在它的右儿子的位置。然后对fa[fa[x]]也进行这样的操作,直到x和根在同一条实链上。
1 | void access(int x) { |
makeroot(x)
利用这个函数我们把x变成全树的根。
首先access(x),然后我们要让x成为实链上的最浅点。注意到此时x恰好是实链上的最深点,那么我们直接对这个splay进行区间翻转操作就可以达到目的。
1 | void makeroot(int x) { access(x),splay(x),Swap(x); } |
link(x,y)
把x置为根,把x所在的splay的根的父亲设置成y就可以了。
1 | void link(int x,int y) { |
cut(x,y)
把x设为根,然后access(y),在splay上把两个点断开就可以了。
1 | void cut(int x,int y) { |
判断两个点之间是否有边,makeroot(x),access(y),splay(y),然后判断ch[x][0]
是否为空。
findroot(x)
找出x这个点所在的树的根。
首先access(x),然后找出x所在实链的最浅点。
操作
区间修改/查询
如果要修改/查询从x到y的链信息,先makeroot(x),然后access(y),此时我们需要的链恰好就是x,y所属的实链。转化成splay的修改和查询就可以了。
子树信息
我们对每一个点,记录一个vir[u],表示它的所有虚儿子的子树信息的和。
我们在splay上维护这个点的splay子树内,所有的点及其虚子树的信息的和。
1 | sum[u]=sum[ch[u][0]]+sum[ch[u][1]]+vir[u]+val[u]; |
只有轻重链切换的时候,我们需要考虑vir[u]的修改。
1 | void access(int x) { |
求两个点的lca
首先access(x),然后执行access(y)。注意到lca(x,y)就是我们在access(y)的过程中跳到最后一条实链上,我们跳到的那个点。
1 | int access(int x) { |
当然,操作是无穷多的,只要你脑洞足够大hhh
模板
tree II
1 |
|