Link-Cut-Tree詳解


圖片參考YangZhe的論文FlashHu大佬的博客

區別在於虛實是可以動態變化的,因此要使用更高級、更靈活的Splay來維護每一條由若干實邊連接而成的實鏈

請先學習Splay之后再閱讀本文

  • 查詢、修改鏈上的信息(最值,總和等)

  • 隨意指定原樹的根(即換根)

  • 動態連邊、刪邊

  • 動態維護連通性

  • 更多毒瘤操作

1.每一個Splay維護的是一條從上到下按在原樹中深度嚴格遞增的路徑,且中序遍歷Splay得到的每個點的深度序列嚴格遞增

2.每個節點包含且僅包含於一個Splay中

3.邊分為實邊和虛邊,實邊包含在Splay中,而虛邊總是由一棵Splay指向另一個節點(指向該Splay中中序遍歷最靠前的點在原樹中的父親)

因為性質2,當某點在原樹中有多個兒子時,只能向其中一個兒子拉一條實鏈(只認一個兒子),而其它兒子是不能在這個Splay中的

那么為了保持樹的形狀,我們要讓到其它兒子的邊變為虛邊,由對應兒子所屬的Splay的根節點的父親指向該點,而從該點並不能直接訪問該兒子(認父不認子)

一、access

假設有一珂樹,有一棵樹,一開始實邊和虛邊是這樣划分的(虛線為虛邊)

1309909-20180123095924037-1618037447.png

那么所構成的LCT可能會長這樣(綠框中為一個Splay,可能不會長這樣,但只要滿足中序遍歷按深度遞增(性質1)就對結果無影響)

1309909-20180123095955350-1680422636.png

現在我們要access(N),把A~N的路徑拉起來變成一條Splay

因為性質2,該路徑上其它鏈都要給這條鏈讓路,也就是把每個點到該路徑以外的實邊變虛

所以我們希望虛實邊重新划分成這樣

1309909-20180123101901740-2118178734.png

那么如何實現這個過程呢?

首先把splay(N),使之成為當前Splay中的根

為了滿足性質2,原來N~O的重邊要變輕

因為按深度O在N的下面,在Splay中O在N的右子樹中,所以直接單方面將N的右兒子置為0(認父不認子)

然后就變成了這樣——

1309909-20180123110136115-1112016464.png

我們接着把N所屬Splay的虛邊指向的I(在原樹上是L的父親)也轉到它所屬Splay的根,splay(I)

原來在I下方的重邊I~K要變輕(同樣是將右兒子去掉)

這時候I~L就可以變重了。因為L肯定是在I下方的(剛才L所屬Splay指向了I),所以I的右兒子置為N,滿足性質1。

1309909-20180123110156272-1242463729.png

或許看了這些聰明的你就能發現規律

剩下的步驟自己腦補

想使一個點到根之間的路徑在同一個Splay中只需要循環執行以下操作:

1.轉到根
2.換兒子
3.跟新
4.當前操作點切換為輕邊所指的父親
	inline void pushup(register int x)
	{
		xr[x]=xr[c[x][0]]^xr[c[x][1]]^val[x];
	}
	inline void pushdown(register int x){
		if(rev[x])
		{
			register int l=c[x][0],r=c[x][1];
			rev[l]^=1,rev[r]^=1,rev[x]^=1;
			Swap(c[x][0],c[x][1]);
		}
	}
	inline bool isroot(register int x)
	{
		return c[fa[x]][0]!=x&&c[fa[x]][1]!=x;
	}
	inline void rotate(register int x)
	{
		int y=fa[x],z=fa[y],l,r;
		l=c[y][0]==x?0:1;
		r=l^1;
		if(!isroot(y))
			c[z][c[z][0]==y?0:1]=x;
		fa[x]=z;
		fa[y]=x;
		fa[c[x][r]]=y;
		c[y][l]=c[x][r];
		c[x][r]=y;
		pushup(y),pushup(x);
	}
	inline void splay(register int x)
	{
		top=1;
		q[top]=x;
		for(register int i=x;!isroot(i);i=fa[i])
			q[++top]=fa[i];
		for(register int i=top;i;--i)
			pushdown(q[i]);
		while(!isroot(x))
		{
			int y=fa[x],z=fa[y];
			if(!isroot(y))
				rotate((c[y][0]==x)^(c[z][0]==y)?(x):(y));
			rotate(x);
		}
	}
	inline void access(register int x)
	{
		for(register int t=0;x;t=x,x=fa[x])
		{
			splay(x);
			c[x][1]=t;
			pushup(x);
		}
	}

pushdown就跟懶標記差不多(珂以先不看)

二、makeroot

makeroot定義為換根,讓指定點成為原樹的根

這時候就利用到access(x)和Splay的翻轉操作

access(x)后x在Splay中一定是深度最大的點。

splay(x)后,x在Splay中將沒有右子樹(性質1)。於是翻轉整個Splay,使得所有點的深度都倒過來了,x沒了左子樹,反倒成了深度最小的點(根節點),達到了我們的目的

	inline void makeroot(register int x)
	{
		access(x);
		splay(x);
		rev[x]^=1;
	}

三、findroot

找x所在原樹的樹根,主要用來判斷兩點之間的連通性(findroot(x)==findroot(y)表明x,y在同一棵樹中)

inline int findroot(register int x)
	{
		access(x);
		splay(x);
		while(c[x][0])
			x=c[x][0];
		return x;
	}

在x,y兩點之間連邊

只在保證題目數據合法的情況下才能使用(不一定合法的話先要判聯通(findroot))

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

五、cut

將x,y之間的邊切斷

	inline void split(register int x,register int y)
	{
		makeroot(x);
		access(y);
		splay(y);
	}
	inline void cut(register int x,register int y)
	{
		split(x,y);
		if(c[y][0]==x)
		{
			c[y][0]=0;
			fa[x]=0;
		}
	}

完整代碼

#include <bits/stdc++.h>
#define N 300005
#define getchar nc
using namespace std;
inline char nc(){
    static char buf[100000],*p1=buf,*p2=buf; 
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++; 
}
inline int read()
{
    register int x=0,f=1;register char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return x*f;
}
inline void write(register int x)
{
    if(!x)putchar('0');if(x<0)x=-x,putchar('-');
    static int sta[20];register int tot=0;
    while(x)sta[tot++]=x%10,x/=10;
    while(tot)putchar(sta[--tot]+48);
}
inline void Swap(register int &a,register int &b)
{
    a^=b^=a^=b;
}
int n,m,val[N];
struct Link_Cut_Tree{
	int c[N][2],fa[N],top,q[N],xr[N],rev[N];
	inline void pushup(register int x)
	{
		xr[x]=xr[c[x][0]]^xr[c[x][1]]^val[x];
	}
	inline void pushdown(register int x){
		if(rev[x])
		{
			register int l=c[x][0],r=c[x][1];
			rev[l]^=1,rev[r]^=1,rev[x]^=1;
			Swap(c[x][0],c[x][1]);
		}
	}
	inline bool isroot(register int x)
	{
		return c[fa[x]][0]!=x&&c[fa[x]][1]!=x;
	}
	inline void rotate(register int x)
	{
		int y=fa[x],z=fa[y],l,r;
		l=c[y][0]==x?0:1;
		r=l^1;
		if(!isroot(y))
			c[z][c[z][0]==y?0:1]=x;
		fa[x]=z;
		fa[y]=x;
		fa[c[x][r]]=y;
		c[y][l]=c[x][r];
		c[x][r]=y;
		pushup(y),pushup(x);
	}
	inline void splay(register int x)
	{
		top=1;
		q[top]=x;
		for(register int i=x;!isroot(i);i=fa[i])
			q[++top]=fa[i];
		for(register int i=top;i;--i)
			pushdown(q[i]);
		while(!isroot(x))
		{
			int y=fa[x],z=fa[y];
			if(!isroot(y))
				rotate((c[y][0]==x)^(c[z][0]==y)?(x):(y));
			rotate(x);
		}
	}
	inline void access(register int x)
	{
		for(register int t=0;x;t=x,x=fa[x])
		{
			splay(x);
			c[x][1]=t;
			pushup(x);
		}
	}
	inline void makeroot(register int x)
	{
		access(x);
		splay(x);
		rev[x]^=1;
	}
	inline int findroot(register int x)
	{
		access(x);
		splay(x);
		while(c[x][0])
			x=c[x][0];
		return x;
	}
	inline void split(register int x,register int y)
	{
		makeroot(x);
		access(y);
		splay(y);
	}
	inline void cut(register int x,register int y)
	{
		split(x,y);
		if(c[y][0]==x)
		{
			c[y][0]=0;
			fa[x]=0;
		}
	}
	inline void link(register int x,register int y)
	{
		makeroot(x);
		fa[x]=y;
	}
}T; 
int main()
{
	n=read(),m=read();
	for(register int i=1;i<=n;++i)
	{
		val[i]=read();
		T.xr[i]=val[i];
	}
	while(m--)
	{
		int opt=read();
		if(opt==0)
		{
			int x=read(),y=read();
			T.split(x,y);
			write(T.xr[y]),puts("");
		}
		else if(opt==1)
		{
			int x=read(),y=read();
			if(T.findroot(x)!=T.findroot(y))
				T.link(x,y);
		}
		else if(opt==2)
		{
			int x=read(),y=read();
			T.cut(x,y);
		}
		else
		{
			int x=read(),y=read();
			T.access(x);
			T.splay(x);
			val[x]=y;
			T.pushup(x);
		}
	}
	return 0;
} 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM