Link-Cut-Tree 動態樹演算法
阿新 • • 發佈:2019-02-11
#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); } }