1. 程式人生 > >以 BZOJ 2002 為例學習有根樹LCT(Link-Cut Tree)

以 BZOJ 2002 為例學習有根樹LCT(Link-Cut Tree)

接下來 int 操作 sel com char 如何實現 etc 慢慢

以BZOJ 2002 彈飛綿羊為例學習有根樹LCT(Link-Cut Tree)


註:本文非常簡單,只涉及有根樹LCT,對於無根樹,LCT還有幾個本文沒有提到的操作,以後慢慢更新 =v=

知識儲備

  • [x] splay
  • [x] 樹鏈剖分

題意

有一棵\(n\)個節點的有根樹,動態修改父子關系(保證仍是一棵有根樹),並詢問某節點深度。

題解

這是一道LCT(Link-Cut Tree)的模板題。

Link-Cut Tree (又)是Tarjan發明的一種算法,可以解決一類動態樹問題。“動態樹問題”就是像本題這種,隨時修改樹的結構的問題。

LCT在思想上有些類似於樹鏈剖分。

回顧一下樹剖:

技術分享圖片

在樹剖中,我們把邊分為重邊和輕邊兩種,重邊相連形成了重鏈,而重鏈可以用線段樹維護。

美中不足的是,線段樹無法完成“動態修改”的需要。那麽什麽數據結構最靈活,能隨時斷開/連接呢?那當然是——splay啦。

所以,LCT就是用好多棵splay平衡樹來維護原樹上的每條“實鏈”(類似樹剖的“重鏈”)。

具體是如何維護的呢?可以看這幅圖:

技術分享圖片

左圖是原樹,右圖是我們建出的splay樹。

左圖中的每一條實鏈都對應著右圖中一棵“完整的”(不帶“箭頭”的)平衡樹,如(1,2,3)、(4,5)、(6)各自組成一棵平衡樹。每棵完整的平衡樹都把節點在原樹中的深度作為關鍵字,例如4比5深度小,所以是5的左兒子;3比1深度大,所以是1的右兒子。當然,身為splay,右圖不是左圖對應的唯一平衡樹,還有很多種可能的合法平衡樹。

而左圖上的每一條虛邊(u, v),則使用了一種“單向邊”來維護:設u是父親、v是兒子,則在右圖中,v所在的平衡樹的根節點“認”u所在的平衡樹的根節點為父親,而反過來,父親卻不認兒子。這種關系在右圖中用單向的箭頭表示了。例如,在原圖中,(1, 4)是一條虛邊,4所在的平衡樹的根節點是5,1所在的平衡樹的根節點是1,所以右圖中fa[5] = 1,而1卻不認為5是它的兒子。

好的,現在你對LCT如何實現有了個初步的印象了!那麽接下來,我們逐個學習LCT涉及到的操作。

操作1:Access

Access操作是所有LCT操作的基礎!Access(u)會把根節點到u路徑上的所有邊都變成“實邊”,使得u和根節點處於一棵完整平衡樹中。

代碼實現:

void access(int u){
    int v = 0; //一開始v是空節點,其余時刻v都是要與u連接的小平衡樹的根節點
    while(u){
        splay(u); //將u在旋至它所在的平衡樹的根節點
        rs[u] = v; //因為v深度更大,所以讓v作為u的右兒子
        upt(u); //修改兒子後,莫忘調用單節點更新函數
        v = u, u = fa[u];
    }
}

是不是不算很難吶~

操作2:Cut

顧名思義,Cut就是切斷原樹中的一條邊。下面代碼中,Cut(u)表示切斷u和父親的連邊。這道題是有根樹,非常簡單——先Access(u),使u和跟節點處在同一平衡樹中,然後完全切斷u向fa[u]連的那條邊,即使得“父子互不相認”即可。

void cut(int u){
    access(u);
    splay(u);
    fa[ls[u]] = 0, ls[u] = 0;
    upt(u);
}

顧名思義,Link就是綠帽子林克將兩個節點連到一起,即在原樹中加邊的操作。

這道題是有根樹,有根樹中Link操作非常簡單:Link(u, v)表示把u作為v的兒子——那麽直接令fa[u] = v即可。

void link(int u, int v){
    cut(u);void cut(int u){
    access(u);
    splay(u);
    fa[ls[u]] = 0, ls[u] = 0;
    upt(u);
}
    fa[u] = v;
}

有根樹LCT涉及的基本操作只有這三條!只要寫個splay、再寫這三個函數,BZOJ 2002 彈飛綿羊這道題就很好寫啦!

附上我的代碼(大部分是Copycat企鵝學長的……)

#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
#define enter putchar('\n')
#define space putchar(' ')
template <class T>
void read(T &x){
    char c;
    bool op = 0;
    while(c = getchar(), c > '9' || c < '0')
    if(c == '-') op = 1;
    x = c - '0';
    while(c = getchar(), c >= '0' && c <= '9')
    x = x * 10 + c - '0';
    if(op) x = -x;
}
template <class T>
void write(T x){
    if(x < 0) putchar('-'), x = -x;
    if(x >= 10) write(x / 10);
    putchar('0' + x % 10);
}
const int N = 200005;
int n, m, ta, tb;
int fa[N], ls[N], rs[N], sze[N];
#define isroot(u) (ls[fa[u]] != (u) && rs[fa[u]] != (u))
#define which(u) (ls[fa[u]] == (u))
void upt(int u){
    sze[u] = sze[ls[u]] + sze[rs[u]] + 1;
}
void rotate(int u){
    int v = fa[u], w = fa[v], b = which(u) ? rs[u]: ls[u];
    if(!isroot(v)) which(v) ? ls[w] = u: rs[w] = u;
    which(u) ? (ls[v] = b, rs[u] = v): (rs[v] = b, ls[u] = v);
    fa[u] = w, fa[v] = u;
    if(b) fa[b] = v;
    upt(v), upt(u);
}
void splay(int u){
    while(!isroot(u)){
    if(!isroot(fa[u])){
        if(which(u) == which(fa[u])) rotate(fa[u]);
        else rotate(u);
    }
    rotate(u);
    }
}
void access(int u){
    int v = 0;
    while(u){
    splay(u);
    rs[u] = v;
    upt(u);
    v = u, u = fa[u];
    }
}
void cut(int u){
    access(u);
    splay(u);
    fa[ls[u]] = 0, ls[u] = 0;
    upt(u);
}
void link(int u, int v){
    cut(u);
    fa[u] = v;
}
int main(){
    read(n);
    for(int i = 1; i <= n; i++)
    read(ta), sze[i] = 1, fa[i] = ta + i > n ? 0 : ta + i;
    read(m);
    while(m--){
    read(ta);
    if(ta == 1){
        read(ta), ta++;
        access(ta);
        splay(ta);h
        write(sze[ls[ta]] + 1), enter;
    }
    else{
        read(ta), read(tb), ta++;
        link(ta, ta + tb > n ? 0 : ta + tb);
    }
    }
    return 0;
}

以 BZOJ 2002 為例學習有根樹LCT(Link-Cut Tree)