LCT简要总结

实链剖分

简单来说就是把边分为实边和虚边,然后做不同的维护,不过是可以动态维护。

这样可以减小操作数,可以用势能分析证明时间复杂度,先欠着


LCT 中分了实边和虚边,对实边构成的实链用 Splay 维护,然后通过虚边把 Splay 连起来,因为维护的是一条连续的链,所以满足中序遍历 Splay 得到的点按深度严格递增。

因为实链是一条链,所以每个节点最多和 2 个实点(祖先和儿子)相连,所以 Splay 中左儿子维护原树的祖先,右儿子维护儿子。

注意 LCT 在实链中是父子互认的,但虚边是只认父不认子的。


LCT的基本操作

  • nort

判断一个点是不是当前实链的顶端。

若 x 是当前实链的顶端,那它连向 fa 的边一定是虚边,且 fa 的儿子一定不会有 x。

反之则不是。

bool nort(int x){return lc(fa(x))==x||rc(fa(x))==x;}
  • rotate&&splay

和普通的 Splay 没啥区别,不过要 nort 特判一下,代码不放了。

  • access

这是 LCT 最核心的操作了。

打通并分离出从 x 到根节点的路径(全变为实边),以便于维护。

操作:

1.把 x 旋到它所在的 Splay 的根。

2.把 x 的右儿子设为上一轮处理的节点 y(把 x 的实边连向 y,原本的设为虚边)。

3.通过 \(fa_x\) 不断往上跳到父亲节点,直到根节点。

void access(int x){for(int y=0;x;x=fa(y=x)) splay(x),rc(x)=y,up(x);}
  • makert

把 x 设为 LCT 的根。

\(access(x)\) 打通一下。

这时 x 一定是最右边的点,即中序遍历的最后一个点(深度最深)。

所以把 x 旋到根节点后,做一次翻转操作,x 就变成中序遍历的第一个点即根节点了。

void makert(int x){access(x),splay(x),rev(x)^=1;}
  • findrt

找到 x 所在原树的根,用来判断联通性。

\(access(x)\) 打通并 \(Splay(x)\) 一下,这时 x 和 根节点在同一个 Splay 上。

而根节点一定是中序遍历的第一个点(最左的点),所以从 x 不断往左儿子走,走到尽头就是根节点。

int findrt(int x){
  access(x),splay(x);
  while(lc(x)) down(x),x=lc(x);
  return splay(x),x;
}

把 x 与 y 连一条边。

把 x 设为根节点,然后判一下 x 与 y 联不联通,不联通就把 \(fa_x\) 设为 y(连一条虚边)。

void link(int x,int y){makert(x);if(findrt(y)!=x) fa(x)=y;}
  • cut

删除 x 与 y 的边。

把 x 设为根节点。

这时若 y 与 x 有边相连,则有:

  1. y 所在的根为 x(联通)。

  2. y 的 fa 为 x(有边相连)。

  3. y 没有左儿子,否则 \(dep_x<dep_{lc_y}<dep_y\),就不相连。

然后直接断掉即可。

void cut(int x,int y){makert(x);if(findrt(y)==x&&fa(y)==x&&!lc(y)) fa(y)=rc(x)=0;up(x);}

模版

code
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
#define rep(i,l,r) for(int i=(l);i<=(r);++i)
#define per(i,r,l) for(int i=(r);i>=(l);--i)

const int N=2e5+5;
class LCT{
private:
	struct tree{int c[2],v,f,s;bool r;}t[N<<2];
	#define ch(x,op) t[x].c[op]
	#define val(x) t[x].v
	#define sum(x) t[x].s
	#define rev(x) t[x].r
	#define fa(x) t[x].f 
	bool nort(int x){return ch(fa(x),0)==x||ch(fa(x),1)==x;}
	void up(int x){sum(x)=val(x)^sum(ch(x,0))^sum(ch(x,1));}
	void down(int x){if(rev(x)) swap(ch(x,0),ch(x,1)),rev(ch(x,0))^=1,rev(ch(x,1))^=1,rev(x)=0;}
	void push(int x){if(nort(x)) push(fa(x));down(x);}
	void rotate(int x){
		int y=fa(x),z=fa(y),k=(ch(y,1)==x),w=ch(x,k^1);
		if(nort(y))ch(z,ch(z,1)==y)=x;
		ch(y,k)=w,ch(x,k^1)=y;
		if(w)fa(w)=y;fa(x)=z,fa(y)=x;
		up(y),up(x); 
	}
	void splay(int x){
		push(x);
		while(nort(x)){
			int y=fa(x),z=fa(y);
			if(nort(y)) rotate(ch(z,1)==y^ch(y,1)==x?x:y);
			rotate(x);
		}
	}
	void access(int x){for(int y=0;x;x=fa(y=x)) splay(x),ch(x,1)=y,up(x);}
	void makert(int x){access(x),splay(x),rev(x)^=1;}
	int findrt(int x){
		access(x),splay(x);
		while(ch(x,0)) down(x),x=ch(x,0);
		return splay(x),x;
	}
	void split(int x,int y){makert(x),access(y),splay(y);}
public:
	void init(int l,int r){rep(i,l,r)cin>>val(i);}
	void link(int x,int y){makert(x);if(findrt(y)!=x)fa(x)=y;}
	void cut(int x,int y){makert(x);if(findrt(y)==x&&fa(y)==x&&!ch(y,0))fa(y)=ch(x,1)=0;up(x);}
	void change(int x,int val){splay(x),val(x)=val,up(x);}
	int query(int x,int y){return split(x,y),sum(y);}
	#undef ch
	#undef val
	#undef sum
	#undef rev
	#undef fa
}LCT; 
int n,m,op,x,y;
void solve(){
	cin>>n>>m;
	LCT.init(1,n);
	while(m--){
		cin>>op>>x>>y;
		switch(op){
			case 0:cout<<LCT.query(x,y)<<'\n';break;
			case 1:LCT.link(x,y);break;
			case 2:LCT.cut(x,y);break;
			case 3:LCT.change(x,y);break;
		}
	}
}
int main(){solve();}
posted @ 2026-06-09 16:09  lzx20120124  阅读(8)  评论(0)    收藏  举报