Loading

算法初探 - Link-Cut Tree

更新记录

【1】2020.10.18-16:34

  • 1.完善内容

正文

如果给你一棵树,并对这棵树做两种操作

  • 改变某点的权值
  • 询问路径上的异或和

这个时候我们可以用树链剖分简简单单的秒掉这道题

那如果再加上两种操作呢?

  • 连接两点
  • 断开两点

这个时候就不能再用树链剖分了,这种动态树问题有一个专门的算法:Link-Cut Trees

LCT维护的是一个森林!所以一定要分清原树和辅助树(Auxiliary Tree,这里指一条重链上所有点构成的Splay)

毒瘤Tarjan为创始人之一

这个算法依然要用到链剖分这个思想,但是用的不是重链剖分,而是实链剖分

为啥不用重链剖分呢

因为一连边断边这棵树的形态就改变了,重链剖分当然就废了

而实链剖分是可以动态变化的

剖分的思想很简单:自己指定一条边为实边,剩下的就是虚边了

我们规定用实边连接的为实儿子,虚边连接的为虚儿子

虚边怎么表示呢?

假设 f 为节点 n 的父节点
那么从 n 可以访问到 f
但是 f 无法访问 n

这是因为有可能一个节点会有很多虚儿子,如果 f 能访问 n Splay就不是二叉树了

重要性质

每个Splay维护的路径上的点在原树中深度严格递增,且这个Splay的中序遍历得到的序列也是严格递增

代码解释

宏定义

#define function(l,n) inline l n
#define R register int

变量

struct Node{
	int f,son[2],v;
//父节点,子节点,值
	bool re;
//翻转标记
}t[N];

各种操作

nroot

not root的缩写

判断一个节点是否为所在的Splay的树根

  • 是的话返回false
  • 不是的话返回true
function(bool,nroot)(int p) {return t[t[p].f].son[0]==p||t[t[p].f].son[1]==p;}

confirm

判断此节点是父节点的左子节点还是右子节点

function(bool,confirm)(int p) {return t[t[p].f].son[1]==p;}

access

由于存在实虚边,所以我们不能保证两点在一棵Splay上

所以我们需要一种操作来使得两点在一棵Splay上

假设我们access(x,y)

它的含义是以点x开始进行中序遍历,到y结束,且遍历得到的序列满足上文所说的性质

我们不断地去将一个点splay到当前所在的Splay的根

然后根据虚边跳转

之后根据LCT的性质重置子节点

以此类推,直到原树根节点

function(void,access)(int p){
	int son=0;
	do{
		splay(p);
		t[p].son[1]=son;
		pushup(p);
	} while(p=t[son=p].f);
}

setroot

将一个点p设为LCT的根节点

我们想:如果只是splay的话,可能当前的节点与LCT的根节点不连通,所以我们先要access一下,之后splay

但此时这个点p为深度最大的点,也就是说它没有右子树

所以我们要reverse翻转,此时p为深度最小的点,也就是根节点了

function(void,setroot)(int p) {access(p);splay(p);reverse(p);}

findroot

首先access一下,此时根节点一定是p所在的Splay的最小节点

我们将p旋转到它所在的Splay的根,之后一路向左找就好

为了保证它的复杂度,我们最后还要再splay一次

function(int,findroot)(int p){
	access(p);splay(p);
	while(t[p].son[0]) pushdown(p),p=t[p].son[0];
	splay(p);
	return p;
}

setroot让x点成为它所在的Splay的根,之后我们判断一下连通性之后连一条虚边即可

function(void,link)(int x,int y){
	setroot(x);
	if(findroot(y)!=x) t[x].f=y;
}

首先setroot

之后考虑什么时候才能cut断边

  • 两点联通
  • 它们中序遍历相邻

中序遍历相邻意即它们为父子关系并且y没有左子树

cut

function(void,cut)(int x,int y){
	setroot(x);
	if(x==findroot(y)&&x==t[y].f&&!t[y].son[0]){
		t[y].f=t[x].son[1]=0;
		pushup(x);
	}
}

split

获取x - y的这条链,之后我们就可以方便的维护信息了

function(void,split)(int x,int y) {setroot(x);access(y);splay(y);}
#include<iostream>
#define function(l,n) inline l n
#define R register int
#define N 1000001
using namespace std;
int n,m,v[N],op,x,y,st[N];
struct Node{
	int f,son[2],v;
	bool re;
}t[N];
function(bool,nroot)(int p) {return t[t[p].f].son[0]==p||t[t[p].f].son[1]==p;}
function(bool,confirm)(int p) {return t[t[p].f].son[1]==p;}
function(void,pushup)(int p) {t[p].v=t[t[p].son[0]].v^t[t[p].son[1]].v^v[p];}
function(void,reverse)(int p) {swap(t[p].son[0],t[p].son[1]);t[p].re^=1;}
function(void,connect)(int up,int down,bool r) {t[up].son[r]=down;}
function(void,pushdown)(int p){
	if(!t[p].re) return;
	if(t[p].son[0]) reverse(t[p].son[0]);
	if(t[p].son[1]) reverse(t[p].son[1]);
	t[p].re=0;
}
function(void,rotate)(int p){
	int fa=t[p].f,gfa=t[fa].f,np=confirm(p),ot=t[p].son[!np];
	if(nroot(fa)) t[gfa].son[confirm(fa)]=p;
	connect(p,fa,!np);
	connect(fa,ot,np);
	t[fa].f=p;t[p].f=gfa;
	if(ot) t[ot].f=fa;
	pushup(fa);
}
function(void,splay)(int p){
	int fa=p,size=0,gfa;
	st[++size]=fa;
	while(nroot(fa)) st[++size]=fa=t[fa].f;
	while(size) pushdown(st[size--]);
	while(nroot(p)){
		fa=t[p].f;gfa=t[fa].f;
		if(nroot(fa)) rotate(confirm(p)==confirm(fa)?fa:p);
		rotate(p);
	}
	pushup(p);
}
function(void,access)(int p){
	int son=0;
	do{
		splay(p);
		t[p].son[1]=son;
		pushup(p);
	} while(p=t[son=p].f);
}
function(void,setroot)(int p) {access(p);splay(p);reverse(p);}
function(int,findroot)(int p){
	access(p);splay(p);
	while(t[p].son[0]) pushdown(p),p=t[p].son[0];
	splay(p);
	return p;
}
function(void,link)(int x,int y){
	setroot(x);
	if(findroot(y)!=x) t[x].f=y;
}
function(void,cut)(int x,int y){
	setroot(x);
	if(x==findroot(y)&&x==t[y].f&&!t[y].son[0]){
		t[y].f=t[x].son[1]=0;
		pushup(x);
	}
}
function(void,split)(int x,int y) {setroot(x);access(y);splay(y);}
signed main(){
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	cin>>n>>m;
	for(R i=1;i<=n;++i) cin>>v[i];
	for(R i=1;i<=m;++i){
		cin>>op>>x>>y;
		if(!op){
			split(x,y);cout<<t[y].v<<"\n";
		} else if(op==1){
			link(x,y);
		} else if(op==2){
			cut(x,y);
		} else if(op==3){
			splay(x);v[x]=y;
		}
	}
}
posted @ 2020-10-21 17:24  Rutou_kaname  阅读(142)  评论(1编辑  收藏  举报