Link-cut Tree

链剖分

重链剖分

使用静态数据结构维护,按照把子树大小最大的设为重儿子,然后连重边,如此剖分。这样做的好处是,每条路经经过 \(\log n\) 条重边。

实链剖分(LCT)

逻辑基本相同,选某个点的儿子与这个点之间连一条实边。但是更加灵活,比如access操作之后可能一个点和它儿子只有虚边

使用splay维护,bst序越大,这个点就越深。

虚边认父不认子,连接两个splay。

LCT核心操作

基本思想

剖分完之后,使用splay维护,可以动态修改,查询路径,并且维护动态树连通性。

access(x)

从指定节点到根节点,自下往上,打通根节点到指定节点的实链,是的一条从根开始指定节点结束的splay出现

splay旋转路径上对于有实变得点,让他变虚,对于x路径上的点变重。把要改虚的边用旋转去掉。

splay是二叉树,在splay上进行操作

  • 转到根 splay
  • 换儿子
  • 更新信息
  • 当前操作节点切换到轻边所指的父节点
void access(int x){
	for(int y=0;x;y=x,x=t[x].f){
		splay(x);
		t[x].son[1]=y;
		pushup(x);
	}
}

makeroot(x)

将 x 变成原树的根。首先 access(x) 得到树根到 x的路径,然后 splay(x),将x作为splay的根。反转整个splay,所有点的深度倒过来,则x成为新的树根,且因为认父不认子,这个splay的bst序不变,其他splay的信息不变,则树的结构也不变。

void op_rev(int x){
	if(!x)return;
	swap(t[x].son[0],t[x].son[1]);
	t[x].rev ^= 1;
}
void make_root(int x){
	access(x);
	splay(x);
	op_rev(x);
}

findroot(x)

找到 \(x\) 所在连通块的根。首先 access(x),让 \(x\) 与根在同一个splay中。此时根在x的最左子节点,只要循环下去找就行。

void pushdown(int p){
	if(t[p].rev){
		op_rev(t[p].son[0]);
		op_rev(t[p].son[1]);
		t[p].rev=0;
	}
}
int find_root(int x){
	access(x);
	splay(x);
	while(t[x].son[0])pushdown(x),x=t[x].son[0];
	splay(x);
	return x;
}

split(x,y)

将x到y的路径放到一个单独的splay中。首先将 \(x\) 作为跟,然后 access(y),就做到了。然后根不确定,最好将 x 或 y 作为根。/

void split(int x,int y){
	make_root(x);
	access(y);
	splay(x);//有用。不能直接用y的值:根不知道是谁。把x或y作为根,方便操作
}

连一条x-y的边。先判断是否在一个连通块里面(或者题目给出条件跳过判断),然后,将 \(x\) 作为根与 \(y\) 连接。

void link(int x,int y){
	make_root(x);
	if(find_root(y)!=x)t[x].f=y;
}

cut

将x-y边断开。首先判断他们是否相邻有边(或者题目给出条件跳过判断),然后将这条实边割掉。

void cut(int x,int y){
	make_root(x);
	if(find_root(y)==x&&t[y].f==x&&t[y].son[0]==0){
		t[y].f=t[x].son[1]=0;
		pushup(x);
	}
}

注意点

splay 的判根方式要改变,因为有虚边存在。fa不为0也可能是根,要特别写isRoot函数,判断父节点认不认这儿子。认父不认子的悲惨情况可能发生。

bool isRoot(int p){
	return t[t[p].f].son[0]!=p&&t[t[p].f].son[1]!=p;
}

splay的时候要记得从上往下pushdown。

void splay(int x){
	int y=x,z;
	stack<int> stk;
	while(!isRoot(y))stk.emplace(y),y=t[y].f;
	stk.emplace(y);
	while(!stk.empty())pushdown(stk.top()),stk.pop();
	while(!isRoot(x)){
		y=t[x].f,z=t[y].f;
		if(!isRoot(y))
			rotate(t[y].son[1]==x^t[z].son[1]==y?x:y);
		rotate(x);
	}
	pushup(x);
}

时间复杂度

势能分析,单次 access(x) 操作 \(O(\log n)\)

练习题

P1501 提示:懒惰标记,LCT,树

调试经验:pushup时一般不特判儿子为0的情况,所以对于编号为0的节点要进行处理,siz为0,sum为0,不要瞎搞。
计算范围要落到实处,不要口胡,借鉴这篇分析

P3690 提示:LCT,模板

P4234 提示:LCT,排序,双指针

注意判自环呀

闲谈

这可能是我第一次学数据结构如此绝望

Q:不会splay怎么办?A:学,资源很多

splay修改l,r,但是提根要提l-1,r+1

LCT真的不难,写完竟然只有不到200行。

参考资料

FlashHu

SPOJ375 QTREE 解法的一些研究

posted @ 2024-01-25 22:13  妖灵梦  阅读(27)  评论(0)    收藏  举报