1. 程式人生 > >【動態樹】【Link Cut Tree】動態樹的理解(入門)

【動態樹】【Link Cut Tree】動態樹的理解(入門)

引入

現在我們需要一個數據結構滿足支援以下的操作:

  1. 兩個節點連線(保證不出現環)
  2. 兩個節點斷開
  3. 求任意兩個節點之間的區間和

這樣是不是很像樹鏈剖分? 但是因為是動態的所以我們採用動態樹來進行維護。

樣例

現在給出一個樣例,我們一下的解釋都以當前樣例為模板
我們有三個節點1 2 3 4,現在他們是連線在一起的如下圖
這裡寫圖片描述
為什麼有一個虛線呢,這裡我們用虛線表示假裝連線在了一起(這裡下文會講到)

操作

首先我們因為需要維護的是一棵樹,我們利用樹鏈剖分的思路,我們將當前樹分為許多條鏈分別用Splay進行維護,這樣我們就用上圖的表示1->2是類似於輕邊的東西,1->3是類似於重邊的東西,但是這都是臨時的


那麼我們因為需要一條鏈那麼我們需要以下的操作

操作一(一切的基礎)

我們利用Access(u)表示從當前根到u變為重邊,同時讓這條鏈獨立出來,那麼我們可以通過Access(2)得到以下的圖
這裡寫圖片描述
這樣1->2就成為了一條鏈,同時將3孤立出去了

操作二(Splay=神奇的相對位置)

我們通過一個Splay來維護每一條鏈(的深度),在每一條鏈成為一個Splay進行旋轉的時候,位置並不是不改變的對於上圖我們有三個Splay
1、1->2
2、4
3、3
這樣的三棵樹我們在旋轉Splay的同時每一個Splay的根的父親表示的是當前鏈的最頂端的父親(也就是深度最淺的那個節點),並不是Splay的root節點的父親,這樣我們儲存的就是相對位置就像我們使用Splay(2)上面的圖就會轉成
這裡寫圖片描述


通過觀察可以發現其實相對的位置其實是沒有改變的。

操作三(make_root=讓整個樹換個方向吧)

其實我們的動態樹沒有動他會感到寂寞
我們可以發現對於任意一個節點都可以成為新的根,同時其他的都可以跟著翻轉就行了,那麼我們首先想要成為Boss我們第一步就是進入和根連線的中心鏈中,這樣make_root(u)首先我們要Access(u)比如我們讓4成為根,我們需要Access(4)然後就變成了這裡寫圖片描述
看上去這個圖中4在2的左邊兒子其實因為這個Splay我並沒有畫出來,他其實是長成這個樣子的
這裡寫圖片描述
這樣此時我們Splay(4)就成了
這裡寫圖片描述
當然我們發現這個時候我們的2不是還是在4號節點的左邊嗎?但是對於一個平衡樹的概念我們是應該翻轉一下的這樣我們的4就沒有兒子比他深度更淺啦啦啦啦~然後我們給4號節點打上標記rev同時我們將左右兒子交換這裡寫圖片描述


注意我們在Splay(u)的同時要記得從u所在的Splay的根部從上一直到u下放標記同時交換左右子樹

操作四(Link=開始連線吧)

我們如果需要進行連線想一想並查集。。。。我們首先找到x和y的根節點然後讓root(x)成為root(y)的兒子。。。。。這樣我們對於動態樹採取同樣的方法,首先我們make_root(x)這樣x就成為了一個完整的樹的頂端 這樣我們就可以不用考慮x的兒子的問題啦此時Fa(x)一定是等於0的那麼我們可以讓Fa(x)=y好啦就是這樣。。。
同樣我們舉個例子假設我們有1, 2成為一棵樹, 3, 4成為了一棵樹,那麼我們如下:那麼Link(2, 4)
這裡寫圖片描述
我們得到首先:make_root(2)
這裡寫圖片描述
然後設定Fa(2)=4
這裡寫圖片描述
這裡我們的rev2是作為4的左邊兒子出現的因為為了維護之前的相對位置是鏈的頂端的性質,同理我們有

操作五(斷開吧)

既然可以連線我們一定可以斷開天下沒有不散的宴席這樣我們可以得到斷開的方法,首先Cut(x,y)表示斷開x和y的連線,那麼我們同理首先make_root(x)這樣x就成為了整個樹的頂端此時用Access(y)那麼y和x一定是在同一條鏈上然後我們使用Splay將y旋轉到頂部,因為x和y一定相連,同時Splay並不會干擾到x的深度,那麼在Splay之後x的深度一定是y的深度+1同時因為x和y相連所以我們判斷x和y一定是x是y的左兒子(為什麼不是右兒子那是因為我們之前已經make_root(x)所以可以保證沒有深度比x更淺的啦啦啦啦啦~~~~

程式碼

程式碼時間到:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
const int MAXN = 10000;
int ch[MAXN+10][2], Fa[MAXN+10], rev[MAXN+10], que[MAXN+10];
bool isroot(int u){
    if(!Fa[u]) return 1;
    return ch[Fa[u]][0] != u && ch[Fa[u]][1] != u;
}
void Rotate(int u){
    bool d = ch[Fa[u]][1] == u;
    int x = Fa[u], y = Fa[x];
    Fa[u] = y;
    if(!isroot(x)) ch[y][ch[y][1] == x] = u;
    ch[x][d] = ch[u][!d]; Fa[ch[u][!d]] = x;
    ch[u][!d] = x; Fa[x] = u;
}
void push_down(int u){
    if(rev[u]){
        rev[ch[u][0]]^=1;
        swap(ch[ch[u][0]][0], ch[ch[u][0]][1]);
        rev[ch[u][1]]^=1;
        swap(ch[ch[u][1]][0], ch[ch[u][1]][1]);
        rev[u] ^= 1;
    }
}
void Splay(int u){
    int top = 0;
    que[++top] = u;
    for(int i=u;!isroot(i);i=Fa[i])
        que[++top] = Fa[i];
    top++;
    while(--top) push_down(que[top]);
    while(!isroot(u)){
        int x = Fa[u], y = Fa[x];
        if(!isroot(x)){
            if((ch[y][0] == x) ^ (ch[x][0] == u)) Rotate(x);
            else Rotate(u);
        }
        Rotate(u);
    }
}
void Access(int u){
    int t = 0;
    while(u){
        Splay(u);
        ch[u][1] = t;
        t = u;
        u = Fa[u];
    }
}
void reset_root(int u){
    Access(u);
    Splay(u);
    rev[u] ^= 1;
    swap(ch[u][0], ch[u][1]);
}
void Link(int x, int y){
    reset_root(x);
    Fa[x] = y;
    Splay(x);
}
void Cut(int x, int y){
    reset_root(x);
    Access(y);
    Splay(y);
    ch[y][0] = Fa[x] = 0;
}

感謝

感謝您閱讀這篇文章,如果有不足的地方請評論