Link-Cut-Tree 動態樹算法總結
動態樹是一類要求維護森林連通性的算法總稱,其中最常用的就是lct (Link-Cut-Tree).
lct 支持一下操作
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 2000003
using namespace std;
int n,m;
int ch[N][3],fa[N],next[N],size[N],st[N],rev[N],top;
int isroot(int x) //是否是輔助樹的鏈頂,即當前splay 的根
{
return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;//他父親的左右兒子都不是他,輔助樹的根節點的父親指向鏈頂的父親節點,然而鏈頂的父親節點的兒子並不指向輔助樹的根節點
}
void update(int x)
{
size[x]=size[ch[x][0]]+size[ch[x][1]]+1;
}
void pushdown(int x)
{
if (!x) return;
if (rev[x])
{
swap(ch[x][0],ch[x][1]);
rev[ch[x][0]]^=1; rev[ch[x][1]]^=1; rev[x]=0;
}
}
int get(int x)
{
return ch[fa[x]][1]==x;
}
void rotate(int x)
{
int y=fa[x],z=fa[y],l,r;
l=get(x); r=l^1;
if(!isroot(y))
ch[z][ch[z][1]==y]=x;
ch[y][l]=ch[x][r]; fa[ch[y][l]]=y;//很神奇,這兩行放到if前就會TLE
ch[x][r]=y; fa[y]=x; fa[x]=z;//因爲更改了fa[y]的緣故,單純的splay if 語句中判斷的是z是否爲根,所有不影響,但是lct 中splay與單純的splay有細節上的差別
}
void splay(int x)
{
top=0; st[++top]=x;
for (int i=x;!isroot(i);i=fa[i])
st[++top]=fa[i];
for (int i=top;i>=0;i--) pushdown(st[i]);//由於找節點並非自上至下,故操作之前需預先將節點到輔助樹根的標記全下傳一遍,注意翻轉標記只會影響當前這顆樹,不會改變整顆樹中的順序。
while(!isroot(x))
{
int y=fa[x];
if (!isroot(y)) //判斷y 是否是輔助樹中的根節點
rotate(get(x)==get(y)?y:x); //splay 之字形旋轉
rotate(x);
}
}
void access(int x) //將一個點與原先的重兒子切斷,並使這個點到根路徑上的邊全都變爲重邊,執行Access(x)函數後這個節點到根的路徑上的所有節點形成了一棵Splay,便於操作或查詢節點到根路徑上的所有節點
{
int t=0;
while (x)
{
splay(x);//將x 轉到輔助樹的根節點
ch[x][1]=t; //將x 原來的重兒子斬斷 ,但是x的重兒子並未斬斷與x的關係,也就是重兒子只是當前存儲了當前的路徑,是不斷改變的,下一次詢問時還可以重新通過fa記錄的關係得到一條新的重鏈,保證了原樹的信息
t=x; x=fa[x];
}
}
void rever(int x) //換根,換根換的是原樹的根,是把x在原樹中正常轉動到根結點,在原樹轉動之後,那麼原樹中對應的深度也相應發生了變化,因爲splay維護的是原樹的信息,並且是以深度爲關鍵字建樹,所有樹的形態發生翻轉,以保證可以通過splay還原原樹。有一點需要注意就是原樹其實不需要維護,他是虛擬的不存在的
{
access(x); splay(x); rev[x]^=1; //注意Access(x)之後x不一定是Splay的根節點 所以Access之後通常還要Splay一下
}
void cut(int x,int y) //先把x轉到他所在鏈的根,
{
rever(x); //這裏之所以要把x轉到他所在子樹的根是因爲lct可以維護多棵樹,並支持合併,但是如果連接是x不是他所在樹的根的話,那麼他之前一定有一個父親節點,連接時就會發生混亂。
access(y); splay(y); ch[y][0]=fa[x]=0; //因爲原樹換根後,x,y的位置關係發生了改變,所有y 砍掉的是左兒子,而不是右兒子!!
}
void link(int x,int y) //連接,建立新的父子關係
{
rever(x); fa[x]=y; splay(x);
}
int find (int x) //判斷森林連通性,因爲一顆輔助splay的父親不一定是當前根的父親,而是重鏈的的鏈頂的父親,因爲splay是以深度爲關鍵字建樹,所有我們要不停的向左子樹方向尋找。
{
access(x); splay(x);
while(ch[x][0]) x=ch[x][0];
return x;
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++)
{
char s[10]; int x,y; scanf("%s%d%d",s,&x,&y);
if (s[0]=='Q')
{
if (find(x)==find(y)) printf("Yes\n");
else printf("No\n");
}
else
if (s[0]=='C')
link(x,y);
else
cut(x,y);
}
}