[BZOJ3282]Tree

本文详细介绍了LCT(Lazy-Connect Tree)的数据结构及其核心操作access的实现方式,包括makeroot、link、cut、operate等功能的具体实现代码,并通过实例演示了如何使用LCT解决实际问题。

坑了好久,本来打算就这样弃着,最近又被神秘力量驱使来学这个东西,过程很痛苦但最终还是知道它到底是啥了

它可以用来对森林进行各种操作,为了快速地实现各种操作,我们要像树剖一样把整个森林剖成许多条链,每条链都用splay维护

为了在splay中体现链中节点的相对顺序,splay以节点深度为关键字(从小到大)

注意:就是因为splay以深度为关键字,所以我们只可以把它当成序列数据结构来用,找第$k$大什么的都是不存在的

不同链之间的连接通过splay根节点的父亲来体现

如图中所示,假设链$a$的splay根节点为$p$,链$b$中的节点$x$与链$a$相连,那么我们让$fa_p=x$

把森林剖成这些链之后,我们需要对其进行各种操作,比如形态改变和路径信息查询

所有的操作都基于一个东西:access

access($x$),就是“访问”节点$x$

感性地理解,一般访问树中的节点都要从树根开始走,所以这个操作实际上就是把$x$到根路径上的点都弄到一棵splay里

实现不难,我们需要从$x$按照链一直往上跳,遇到非链的边就把它连到$x$所属的链上,并把涉及到的其他链适当断开

具体点,给一个真代码

void access(int x){
	int y=0;
	while(x){
		splay(x);
		ch[x][1]=y;
		pushup(x);
		y=x;
		x=fa[x];
	}
}

代码中的$y$表示当前链往下一条链的splay根节点,当执行splay($x$)后,$x$以及$x$的左子树都是$x$往上同一条链的点(因为深度$\leq dep_x$),把它连上之前的链(深度$\geq dep_x$),然后往上跳,一直到树根就ok了

如何判断一个点$u$是否为一棵splay的根?当$lson_{fa_u}\neq u$且$rson_{fa_u}\neq u$时,说明$u$是一棵splay的根节点(因为根节点的父亲指向另一条链的节点)

这样我们就完成了lct中的核心操作access,接下来用这个access随意乱搞就可以实现各种功能了

1.makeroot($x$):钦点$x$为树的根

void makeroot(int x){
	access(x);
	splay(x);
	reverse(x);
}

假设原来的根为$r$,因为原来深度从小到大是$r\rightarrow x$,现在变为$x\rightarrow r$,所以深度的相对大小反了,所以需要把整棵splay区间翻转

2.link($x$,$y$):连接一条边$(x,y)$

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

3.cut($x$,$y$):删除边$(x,y)$

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

makeroot($x$)并access($y$)之后,这棵splay中就只有$x$和$y$两个节点了,断开就好

4.operate($x$,$y$):对$x\rightarrow y$这条路径做一些操作(询问,修改等)

void operate(int x,int y){
	makeroot(x);
	access(y);
	splay(y);
	//...
}

makeroot($x$)并access($y$)之后,这棵splay中只有$x\rightarrow y$路径上的点,我们可以对这棵splay做任何平衡树能做的操作

5.connected($x$,$y$):判断$x$和$y$是否在同一棵树中

bool connected(int x,int y){
	if(x==y)return 1;
	makeroot(x);
	access(y);
	splay(y);
	return fa[x]!=0;
}

如果$x$和$y$在同一棵树上,那么在makeroot($x$)并access($y$)之后,$x$和$y$就在同一棵splay上了,再splay($y$),$x$就会被挤下去,即是说$fa_x\ne0$

刚接触的时候会觉得很不可理解,但认真去画一画,调一调就知道它的含义是啥了

另:修改单点时直接在splay上改是可以的

#include<stdio.h>
int fa[300010],ch[300010][2],v[300010],s[300010],r[300010];
#define ls ch[x][0]
#define rs ch[x][1]
void pushup(int x){s[x]=v[x]^s[ls]^s[rs];}
void swap(int&a,int&b){a^=b^=a^=b;}
void rev(int x){
	r[x]^=1;
	swap(ls,rs);
}
void pushdown(int x){
	if(r[x]){
		if(ls)rev(ls);
		if(rs)rev(rs);
		r[x]=0;
	}
}
bool isrt(int x){return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;}
void gao(int x){
	if(!isrt(x))gao(fa[x]);
	pushdown(x);
}
void rot(int x){
	int y,z,f,B;
	y=fa[x];
	z=fa[y];
	f=ch[y][0]==x;
	B=ch[x][f];
	fa[x]=z;
	fa[y]=x;
	if(B)fa[B]=y;
	ch[x][f]=y;
	ch[y][f^1]=B;
	if(ch[z][0]==y)ch[z][0]=x;
	if(ch[z][1]==y)ch[z][1]=x;
	pushup(y);
	pushup(x);
}
void splay(int x){
	int y,z;
	gao(x);
	while(!isrt(x)){
		y=fa[x];
		z=fa[y];
		if(!isrt(y))rot((ch[y][0]==x&&ch[z][0]==y)||(ch[y][1]==x&&ch[z][1]==y)?y:x);
		rot(x);
	}
}
void access(int x){
	int y=0;
	while(x){
		splay(x);
		ch[x][1]=y;
		pushup(x);
		y=x;
		x=fa[x];
	}
}
void makert(int x){
	access(x);
	splay(x);
	rev(x);
}
bool cn(int x,int y){
	if(x==y)return 1;
	makert(x);
	access(y);
	splay(y);
	return fa[x]!=0;
}
void link(int x,int y){
	if(cn(x,y))return;
	makert(x);
	fa[x]=y;
}
void cut(int x,int y){
	makert(x);
	access(y);
	splay(y);
	if(ch[y][0]!=x||ch[x][1]!=0)return;
	fa[x]=0;
	ch[y][0]=0;
	pushup(y);
}
void modify(int x,int a){
	splay(x);
	v[x]=a;
	pushup(x);
}
int query(int x,int y){
	makert(x);
	access(y);
	splay(y);
	return s[y];
}
int main(){
	int n,m,i,x,y;
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;i++){
		scanf("%d",v+i);
		s[i]=v[i];
	}
	while(m--){
		scanf("%d%d%d",&i,&x,&y);
		if(i==0)printf("%d\n",query(x,y));
		if(i==1)link(x,y);
		if(i==2)cut(x,y);
		if(i==3)modify(x,y);
	}
}

555怎么才能让我的旋转变得短一点啊

转载于:https://www.cnblogs.com/jefflyy/p/8082257.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值