1. 程式人生 > >Link-Cut-Tree 動態樹演算法

Link-Cut-Tree 動態樹演算法

#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);  
   }  
}