Link-Cut Tree学习笔记

Link-Cut Tree学习笔记

自己好菜啊,省选之前啥算法都不会,考试把一个\(n\log n\)的LCT题硬是写了个\(n\log^2{n}\).但是跑过去就对了.

所以现在要来重新学习一下\(\text{Link-Cut Tree}\).

定义

一个资磁动态加边,删边,维护信息的数据结构,叫做\(LCT\).

前置知识

Splay

Orz yyb

实链剖分

讲道理我真的不知道实链剖分改叫什么名字.

你考虑对于原来树上面的非叶子节点,指定一个实儿子,其他的都是虚儿子.

与重链剖分和长链剖分不同,实链剖分需要比较快捷的维护,这个时候我们就需要用到\(\text{splay}\).

性质

  1. 每一个\(\text{splay}\)维护的是原树上的一条实链,而且中序遍历按照深度排序.
  2. 每一个节点只会出现在一个\(\text{splay}\)里面.
  3. 实边的儿子会给儿子节点,虚边的儿子只是指向父亲.

操作

前置操作

你需要资磁\(\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\)的时候加上原来的右子树,即把原来的边变成虚边然后加入贡献,新的改成实边的删除贡献.

未完待续

题目会慢慢补的.确信

posted @ 2020-06-03 08:18  fexuile  阅读(87)  评论(0编辑  收藏  举报