動態樹(LCT)初探

前置技能

  • Splay基本操作不是前置技能,你只需要知道Splay是一個排序二叉樹;(由於某些原因,Splay的文章可能會永遠咕掉,也可能明天就出現 逃 ─=≡Σ(((つ•̀ω•́)つ)
  • 指針是前置技能,這時候又要祭出這篇文章了哈哈哈哈哈哈。

那麼直接開始吧

原樹與輔助樹

動態樹(Link Cut Tree,LCT),中文和英文對不上,但是卻反映了這玩意的兩個特徵:

  • 支持動態操作,如不斷地加邊,刪邊;
  • 把樹砍成一條一條的。

這是今天被解剖的樹:
一棵很重要的樹-1
上面這個被稱爲原樹(represented tree)。

每個結點有一個優先兒子(preferred son),這個優先兒子是誰無所謂。每個結點和它的優先結點之間的邊叫優先邊(preferred edge),現在我隨便選優先兒子,然後把所有優先邊加粗:
一棵很重要的樹-2

把每個由粗邊相連的連通塊(包括單點)做成Splay樹,衆所周知,Splay是排序二叉樹,我們需要有個關鍵字,每個結點的關鍵字是它的深度,於是它變成了這樣(當然,每個Splay的結構也可能有變化,這裏給出其中一種):
重要-3
上面這個被稱爲輔助樹(auxiliary tree),也稱爲路徑樹(path tree),我直接叫Splay樹。

然後,將原樹上的細邊(非優先邊)以虛邊的形式接在Splay上(虛邊是僅由兒子向父親連的邊):
這張圖我畫了一年
注意粗邊是有左右兒子之分的,因爲是Splay,虛邊沒有。

來張合照,並確保你看懂了這個合照:
合照

Access操作

含義

意思很簡單,Access(u)表示,把原樹上u到根的所有邊設置成優先邊。與這條路徑衝突的優先邊全部變成非優先邊。

上圖,這是原樹上Access(13)的過程:
Access(13)
合照:
合照
當然,上面這個跟實際的代碼實現無關,我們全部操作都要在輔助樹上完成。

我們要補充Splay樹的Splay操作,如果你已瞭解,請忽略。

Addition: Splay

用Splay的根本目的不是爲了維護序列,而是用Splay操作維護一棵樹的形狀。

旋轉操作

又是這喜聞樂見的圖:
平衡樹的旋轉
也就是說對於Rotate(x)函數,當左兒子時,必須右旋xx;反之,必須左旋xx
你發現這樣一次操作過後,二叉查找樹的性質沒有變,還將xx上提了一層。

研究一下這個操作,只涉及三個點:xxyybbbb是子樹BB的根),說白了就是這兩件事:

  • 互換xxyy的父子關係;
  • bb送給yy(因爲互換父子關係過後,yy必定會空出來一個位置給bb)。

好了旋轉講完了。

Splay操作

Splay(x)表示把x轉到根,一直調用Rotate(x)就行了。

注意可以爲了保持樹的結構好看,有雙旋操作:
這種情況先轉yy再轉xx會讓樹更好看,自己畫畫圖就知道了:
雙旋-1
這種情況則連續轉兩次xx更好看:
雙旋-2
好了Splay講完了。

輔助樹上的Access

給你看一下輔助樹上Access(13)的過程:

Node* Access(Node *u){
    Node *r=NIL;
    while(u!=NIL){
        Splay(u);
        SetChild(u,r,1);
        r=u,u=u->fa;
    }
    return r;
}

(本來代碼的位置都是動圖的)
不想再做動圖了,真的累,,,自己畫畫想想就明白這個過程了(我其實很討厭這句話),,,有人看我再做吧,,,好博客沒人看很煩吶(打廣告於無形之中)。

MakeRoot操作

MakeRoot(u)表示把uu變成原樹的根。
圖就不做了,預計這博客是不可能火的。

void MakeRoot(Node *u){
    Access(u);
    Splay(u);
    u->rev^=1;//Splay的關鍵字是深度,所以MakeRoot(u)過後要翻轉左右子樹,想想就明白了,,,,
}

Link操作

表示在原樹中把uuvv連起來(保證之前uuvv不連通)。

void Link(Node *u,Node *v){
    MakeRoot(u);
    u->fa=v;
}

Cut操作

表示斷開uuvv之間的邊(保證有邊)。

void Cut(Node *u,Node *v){
    MakeRoot(u);
    Access(v);
    Splay(v);
    u->fa=NIL;
    v->ch[0]=NIL;
}

GetRoot操作

得到uu所在的原樹的根(因爲原樹可能是個森林)。

Node* GetRoot(Node *u){
    Access(u);
    Splay(u);
    Node *r=u;
    while(r->ch[0]!=NIL)
        r=r->ch[0];
    Splay(r);
    return r;
}

兩道例題

Query on a tree
Dynamic Tree Connectivity

後記

這篇文章寫到後面就不想寫下去了,做好一篇博客真的很耗時間,但是我絕對敢說我的每篇博客都是網上你能找到的同類博客中最好的之一,我是一個完美主義者,只是想到以前很多真正優秀的博客不能呈現出來,寫博客的動力就沒有了,,,希望大家多支持分享一下,也給我一些動力吧。我退役前可能會有一天來把這篇博客完善好的,如果大家支持的話;當然,也可能永遠停更,如果我覺得寫博客沒有任何意義的話(畢竟,我個人以爲博客對大家的意義遠大於對作者的意義)。感激不盡!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章