Link-Cut Tree学习笔记
Link-Cut Tree学习笔记
自己好菜啊,省选之前啥算法都不会,考试把一个\(n\log n\)的LCT题硬是写了个\(n\log^2{n}\).但是跑过去就对了.
所以现在要来重新学习一下\(\text{Link-Cut Tree}\).
定义
一个资磁动态加边,删边,维护信息的数据结构,叫做\(LCT\).
前置知识
Splay
实链剖分
讲道理我真的不知道实链剖分改叫什么名字.
你考虑对于原来树上面的非叶子节点,指定一个实儿子,其他的都是虚儿子.
与重链剖分和长链剖分不同,实链剖分需要比较快捷的维护,这个时候我们就需要用到\(\text{splay}\).
性质
- 每一个\(\text{splay}\)维护的是原树上的一条实链,而且中序遍历按照深度排序.
- 每一个节点只会出现在一个\(\text{splay}\)里面.
- 实边的儿子会给儿子节点,虚边的儿子只是指向父亲.
操作
前置操作
你需要资磁\(\texttt{pushup,pushdown,reverse,rotate}\)的操作,这里不再赘述,和\(\text{splay}\)维护数列一样.
access操作
首先是一个贼牛逼的操作,考虑我们现在要把一个点到根节点路径上的所有边都变成实链,也就是让路径上的点在一个\(\text{splay}\)里面,代码如下:
void access(int x)
{
for(int y=0;x;y=x,x=t[x].ff)
{
splay(x);t[x].ch[1]=y;pushup(x);
}
}
我们考虑如何修改实链,首先你需要把\(x\)变成\(splay\)的根,以维护第一个性质,不然你改了右儿子但是这个右儿子实际上应该是在父亲的之类的.
然后因为\(y\)的深度比\(x\)大,所以要放在右儿子位置.
最后改了儿子,然后\(pushup\)一下.
findroot操作
int findroot(int x)
{
access(x);splay(x);
while(t[x].ch[0])x=t[x].ch[0];
return x;
}
考虑找到它对应的根,等于是我们把他到根的路径\(access\)出来,然后把他变成\(splay\)的顶端,接着考虑一直往左儿子走,这样子走就是不断把深度变大.
makeroot操作
void makeroot(int x){access(x);splay(x);reverse(x);}
这个时候我们要把\(x\)变成根节点,然后重新定义深度.考虑先把\(x\)节点\(access\)上去然后\(splay\),此时在\(x\)左边的就是原本深度比\(x\)大的,右边因为\(access\)上去了,所以没有右儿子.考虑把儿子\(reverse\)一下,这个时候就满足了我们的要求了.
split操作
我们需要资磁把两个节点的路径抠出来,那么我们可以把一个变成根节点,另一个\(access+splay\)即可.
Link操作
直接一个\(makeroot\)然后连成另一个的虚儿子即可.
Cut操作
首先可以把两个的路径\(split\)出来,此时如果两个点有直接连边的话,显然是\(y\)的左儿子就是\(x\),这个时候我们直接断掉对应关系即可.
Link+Cut代码
void Link(int x,int y){makeroot(x);t[x].ff=y;}
void Cut(int x,int y)
{
split(x,y);
t[y].ch[0]=t[x].ff=0;
}
这就是\(LCT\)的基本操作.
维护子树信息
\(lct\)还可以维护子树信息,这里的子树可以是实子树,也可以是虚子树.
-
如果是维护实子树信息,你需要多写一个\(pushup\),\(splay\)里面多存一个变量表示子树的信息.
-
如果是维护虚子树信息,你需要在\(access\)的时候加上原来的右子树,即把原来的边变成虚边然后加入贡献,新的改成实边的删除贡献.
未完待续
题目会慢慢补的.确信