深入理解LCT

以前做LCT基本上都是拿来当模板用,于是总是会出各种错误,乱搞半天才过...随着玄妙的LCT题的增多,总算渐渐理解了LCT的本质,就记一下好了


定义一下:fa[x]是splay中的父亲,splay的根的fa是另一颗splay中的一个点,即special father


一定要牢记:LCT的splay是按深度为关键字排序,每个splay维护的是一条奇怪的链.

access操作:从x跳到根,将路上散乱的splay合并起来

void access(int x){
	int y=0;
	for(;x;y=x,x=fa[x]){
		splay(x);
		ch[x][1]=y;//将x这个链加入生成的新链中,断掉x在子树中其他的儿子
		upd(x);
	}
}

可以发现,access操作会形成一个只包含x到根所有点的splay.所以如果splay(x),则ch[x][1]为空.

makeroot操作:先access,然后就造了一个x到根的链。由于LCT是按照深度排序,所以做反转操作后x就变成了深度最小,即根.

void makeroot(int x){
	access(x),splay(x);
	tg[x]^=1,pd(x);
}

link操作:比如link(x,y),如果想要把x接到y上,就要makeroot(x),fa[x]=y;

void link(int x,int y){
	makeroot(x),fa[x]=y;
}

如果不makeroot(x),拿access(x),splay(x);来替代的话,那么实际上连接的其实是x所在splay中最左点和y.

这拿深度排序很好解释...

cut操作:cut(x,y),先将x设为根,可以有如下实现.

void cut(int x,int y){
	makeroot(x),access(y),splay(y);
	fa[x]=0,ch[y][0]=0;
	upd(y);
}

由于x,y有边相连,access(y)后根据前面的理论,这个splay里就应该只剩x,y了,且x是y的左儿子.

这时直接断就好了.


有些不换根的题目会让你无法自由地用access,这时cut(x,y)可以这样实现:

假设y=fa[x],那么splay(x)后将x的左儿子接到x的fa上,再将x的左儿子设为0.

即:splay(x),fa[ch[x][0]]=fa[x],ch[x][0]=0;

这样为什么对呢?按照深度排序的理论,断(x,y)就是断x的左子树.这样就很好解释了.


利用access的思想,还可以做什么将x到根赋同一个值的操作:access是将这些点拿出来造一个新的Splay,于是就可以每个splay记个赋值标记.这样通过access的均摊O(logn)的思想就可以降下复杂度.


LCT还可以维护子树信息.可以将x的儿子分为实儿子和虚儿子.实儿子就是splay中的儿子,虚儿子就是原树中的其他边,连向另一些splay.这时拿个什么数据结构来维护这些虚儿子的值,然后access时有断实儿子连虚儿子,link有连虚儿子操作,update要合并虚儿子信息,这些地方改一下即可.


顺便记一下bzoj4530(动态维护子树大小,支持换根)的代码...

#include<bits/stdc++.h>
#define maxn 100100
using namespace std;
int fa[maxn],tg[maxn],n,m,f[maxn],s[maxn];
int ch[maxn][2],sz[maxn],val[maxn];
void pd(int o){
	if(tg[o]){
		swap(ch[o][0],ch[o][1]);
		if(ch[o][0])tg[ch[o][0]]^=1;
		if(ch[o][1])tg[ch[o][1]]^=1;
		tg[o]=0;
	}
}
void upd(int o){
	sz[o]=val[o];
	if(ch[o][0])sz[o]+=sz[ch[o][0]];//,len[o]+=len[ch[o][0]];
	if(ch[o][1])sz[o]+=sz[ch[o][1]];//,len[o]+=len[ch[o][1]];
}
bool isroot(int x){
	if(!x||!fa[x])return true;
	return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;
}
void rotate(int p){
	int q=fa[p],y=fa[q],k=(ch[q][1]==p);
	if(!isroot(q))ch[y][ch[y][1]==q]=p;
	fa[ch[q][k]=ch[p][k^1]]=q;
	fa[ch[p][k^1]=q]=p,fa[p]=y;
	upd(q);
}
void splay(int x){
	int y;
	while(!isroot(x)){
		pd(fa[y=fa[x]]),pd(y),pd(x);
		if(!isroot(y)){
			if((ch[fa[y]][1]==y)^(ch[y][1]==x))rotate(x);
			else rotate(y);
		}
		rotate(x);
	}
	upd(x),pd(x);
}
void access(int x){
	int y=0,fsz=0;
	for(;x;y=x,x=fa[x]){
		splay(x);
		if(ch[x][1])val[x]+=sz[ch[x][1]];
		if(y)val[x]-=sz[y];
		ch[x][1]=y;
		fsz=sz[x],upd(x);
	}
}
void rever(int x){
	access(x),splay(x),tg[x]^=1,pd(x);
}
void clear(int x){
	sz[x]=val[x]=1;
	fa[x]=ch[x][0]=ch[x][1]=tg[x]=0;
}
int find(int x){
	return x==f[x]?x:f[x]=find(f[x]);
}
char op[3];
long long ans=0;
int main(){
	freopen("in.txt","r",stdin);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i)clear(i),f[i]=i,s[i]=1;
	for(int i=1,x,y;i<=m;++i){
		scanf("%s%d%d",op,&x,&y);
		if(op[0]=='A'){
//			access(x),splay(x);
//			access(y),splay(y);
			rever(x),rever(y);
			sz[x]+=sz[y],val[x]+=sz[y],fa[y]=x;
//			x=find(x),y=find(y),f[x]=y,s[y]+=s[x];
		} else {
			rever(x),access(y),splay(y);
	//		if(sz[y]!=s[find(y)])exit(-1);
//			printf("[%d,%d]",sz[y],val[y]);
			printf("%lld\n",1ll*(sz[y]-val[y])*val[y]);
		}
	}
}


PS:一些注意事项

splay后要pushdown&&update

找儿子,找根什么的要pushdown
rotate时update只需一次
有换根操作就不要再想什么玄妙地断父亲的边


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值