LCT總結


LCT總結

類比樹剖,樹剖是通過靜態地把一棵樹剖成若干條鏈然后用一種支持區間操作的數據結構維護(比如線段樹、樹狀數組),而LCT是動態地去處理這個問題。

大家都知道樹剖用線段樹維護,而LCT用\(splay\)維護。實際上同一條重鏈上的所有點才會被放在一棵\(splay\)中,而我們需要同時處理這若干條重鏈對應的若干棵\(splay\)之間的關系。因為一條重鏈上的每個點的深度互異,所以\(splay\)以深度\(dep\)為關鍵字。

我們規定一棵\(splay\)的根的\(fa\)為這條重鏈鏈頂節點在原樹中的父親節點。(前面的那個\(fa\)指的是\(splay\)中的\(fa\)
而顯然認了這個父親之后,父親不會認這個兒子:很簡單,這兩個點不在同一條重鏈上,所以父親的左右兒子一定沒有它。由此可以寫一個判斷一個點是不是根節點(當前\(splay\)的根節點)的函數:

bool isroot(int x){
	return ch[0][fa[x]]!=x&&ch[1][fa[x]]!=x;
}

先講LCT最重要的一個操作:\(access(x)\),表示把\(x\)節點到\(x\)所在樹(連通塊)的根節點之間的路徑全部變成重路徑。

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

相當於是說要把這些在路徑上的點全部搞到一棵\(splay\)里面去。
從下往上做,每次把\(x\)splay到當前\(splay\)的根后,這個\(x\)就會處於一個很微妙的位置:它的右兒子\(rs\)應該會接上這條重鏈下面的一部分,因為這條重鏈下面一部分的深度顯然會大於\(x\)。而所謂“下面的一部分”就是我們上一個處理的\(x\),這也就是為什么y=x,x=fa[x]了。
最開始的\(x\)的下面不應該接上任何東西,所以\(y\)的初值賦為0。

再就是另一個基本操作:\(makeroot(x)\),表示把\(x\)節點設為\(x\)所在樹(連通塊)的根節點。
我們先把\(x\)access了,即連接了\(x\)與根節點,然后再把\(x\)splay到根,那么顯然這個\(x\)肯定會是\(splay\)中的最后一個元素(因為它深度最大),所以我們在\(x\)這里打一個\(rev\)標記(\(splay\)區間翻轉操作)即可。

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

剩下的操作就很簡單了。
\(findroot(x)\),找到\(x\)節點所在樹(連通塊)的根節點。用這個操作可以維護連通性。
\(findroot(x)=access(x)+splay(x)+\)找到最左邊(深度最小)的那個節點。
請注意暴跳了\(x\)之后要把\(x\)splay一下。你可能會問為什么我以前這樣寫沒有被卡,那就請移步[Luogu4230]連環病原體

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

\(split(x,y)\),摳出\(x\)\(y\)的路徑,摳完以后\(y\)\(splay\)的根。
\(split(x,y)=makeroot(x)+access(y)+splay(y)\)

void split(int x,int y){
	makeroot(x);access(y);splay(y);
}

\(cut(x,y)\),砍斷\(x\)\(y\)的邊。
\(cut(x,y)=split(x,y)+\)x的父親和y的左兒子置0。
(split之后這一棵\(splay\)中就只剩\(x\)\(y\)兩個點了,所以才可以這么搞)

void cut(int x,int y){
	split(x,y);fa[x]=ch[0][y]=0;
}

\(link(x,y)\),連接\(x\)\(y\)的邊。
\(cut(x,y)=makeroot(x)+fa[x]=y\)

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

注意:
split請務必保證兩個點聯通
cut請務必保證兩個點直接相連
link請務必保證兩個點不聯通
不然出現玄學錯誤誰都救不了你了

模板

#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 300005;
int n,m,opt,a,b,fa[N],ch[2][N],sum[N],val[N],rev[N],Stack[N],top;
int gi()
{
	int x=0,w=1;char ch=getchar();
	while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
	if (ch=='-') w=0,ch=getchar();
	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
	return w?x:-x;
}
bool son(int x)
{
	return x==ch[1][fa[x]];
}
bool isroot(int x)
{
	return ch[0][fa[x]]!=x&&ch[1][fa[x]]!=x;
}
void pushup(int x)
{
	sum[x]=sum[ch[0][x]]^sum[ch[1][x]]^val[x];
}
void reverse(int x)
{
	if(!x)return;
	swap(ch[0][x],ch[1][x]);rev[x]^=1;
}
void pushdown(int x)
{
	if(!rev[x])return;
	reverse(ch[0][x]);reverse(ch[1][x]);
	rev[x]=0;
}
void rotate(int x)
{
	int y=fa[x],z=fa[y],c=son(x);
	ch[c][y]=ch[c^1][x];if (ch[c][y]) fa[ch[c][y]]=y;
	fa[x]=z;if (!isroot(y)) ch[son(y)][z]=x;
	ch[c^1][x]=y;fa[y]=x;pushup(y);
}
void splay(int x)
{
	Stack[top=1]=x;
	for (int i=x;!isroot(i);i=fa[i])
		Stack[++top]=fa[i];
	while (top) pushdown(Stack[top--]);
	for (int y=fa[x];!isroot(x);rotate(x),y=fa[x])
		if (!isroot(y)) son(x)^son(y)?rotate(x):rotate(y);
	pushup(x);
}
void access(int x)
{
	for (int y=0;x;y=x,x=fa[x])
		splay(x),ch[1][x]=y,pushup(x);
}
void makeroot(int x)
{
	access(x);splay(x);reverse(x);
}
int findroot(int x)
{
	access(x);splay(x);
	while (ch[0][x]) x=ch[0][x];
	splay(x);return x;
}
void split(int x,int y)
{
	makeroot(x);access(y);splay(y);
}
void cut(int x,int y)
{
	split(x,y);fa[x]=ch[0][y]=0;
}
void link(int x,int y)
{
	makeroot(x);fa[x]=y;
}
int main()
{
	n=gi();m=gi();
	for (int i=1;i<=n;i++) val[i]=gi();
	while (m--)
	{
		opt=gi();a=gi();b=gi();
		if (opt==0)
			split(a,b),printf("%d\n",sum[b]);
		if (opt==1)
			if (findroot(a)!=findroot(b))
				link(a,b);
		if (opt==2)
			if (findroot(a)==findroot(b))
				cut(a,b);
		if (opt==3)
			splay(a),val[a]=b,pushup(a);
	}
	return 0;
}

LCT維護子樹信息

LCT還可以維護子樹和,但僅限於維護而不支持修改
一般而言就是用一個數組\(sz[u]\)維護每個節點的所有虛子樹信息之和。
所以虛子樹+實子樹+自己=整個子樹
那么在所有可能導致虛兒子關系變化的地方都要更新\(sz[u]\)
其實僅有的不同只有三個函數:\(pushup,access,link\)
放一下代碼

void pushup(int x)
{
    sum[x]=sum[ch[0][x]]+sum[ch[1][x]]+val[x]+sz[x];
}
void access(int x)
{
    for (int y=0;x;y=x,x=fa[x])
        splay(x),sz[x]+=sum[ch[1][x]]-sum[y],ch[1][x]=y,pushup(x);
}
void link(int x,int y)
{
    makeroot(x);makeroot(y);fa[x]=y;sz[y]+=sum[x];pushup(y);
}

具體可參考BJOI2014大融合cogs2701動態樹


免責聲明!

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



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