1. 程式人生 > 實用技巧 >演算法初探 - Link-Cut Tree

演算法初探 - 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;
		}
	}
}