左偏樹入門

Flower&)發表於2019-01-27

\(\rm{0x01}\) 關於左偏樹

主要是整理自己想出來的幾個梗

  • \(\mathcal{To~be~(left) ~or~not ~to~be~(left), this~is ~a~question}​\) 左偏還是右偏,這是個問題。
  • \(Hell~!~Where~is~my~Left~Leaning~Tree?​\) 該死,我的左偏樹向右偏了。
  • 左偏樹是1個log,右偏樹也是1個log,那我左右都偏是不是就會更快!(恭喜你建出了一棵滿二叉樹)
  • 講個鬼故事:每棵樹都是下偏樹。
  • ……編不出來了

吶,下面進入正題。左偏樹,一種可以合併的堆狀結構,支援\(insert/remove/merge\)等操作。穩定的時間複雜度在\(\Theta(\log n)\)的級別。對於一個左偏樹中的節點,需要維護的值有\(dist\)\(value\)。其中\(value\)不必多說,\(dist\)記錄這個節點到它子樹裡面最近的葉子節點的距離,葉子節點距離為\(0\)

首先,他有以下幾個喜聞樂見的性質:

  • 一個節點的\(value​\)必定(或小於)左、右兒子的\(value​\) (堆性質)
  • 一個節點的左兒子的\(dist\)不小於右兒子的\(dist\) (左偏性質)
  • 一個節點的距離始終等於右兒子\(+1\)

那麼這就可以推出以下性質:

  • 推論:任何時候,節點數為\(n\)的左偏樹,距離最大為\(\log (n+1)-1\)
    \(Proof.\)
    對於一棵距離為定值\(k\)的樹,點數最少時,一定是一棵滿二叉樹。這是顯然的。因為對於每個節點,如果想要有最少的兒子,那麼起碼要做到左兒子的數量等於右兒子的數量。那麼對於他的逆命題也是成立的——“若一棵左偏樹的距離為\(k\),則這棵左偏樹至少有\(2^{k+1}-1\)個節點。”
    所以會有\[n \geq 2^{k+1}-1\],\[\log_2{(n+1)} \geq k+1\], \[\log_2{(n+1)}-1 \geq k\] \(\mathcal{Q.E.D}​\)

\(emmm​\)這可是一個很美妙的性質啊。

\(\rm{0x02}~~\)基本操作

  • \(Merge\)

這是整個左偏樹的重頭戲,時間複雜度穩定在一個\(log\),其主要思想就是不斷把新的堆合併到新的根節點的右子樹中——因為我們的右子樹決定“距離”這個變數,而距離又一定保證在\(~\log~\)的複雜度內,所以不斷向右子樹合併。

大體思路(以小根堆為例),首先我們假設兩個節點\(x\)\(y\)\(x\)的根節點的權值小於等於\(y\)的根節點(否則\(swap(x,y)\)),把\(x\)的根節點作為新樹\(Z\)的根節點,剩下的事就是合併\(x\)的右子樹和\(y\)了。

合併了\(x\)的右子樹和\(y\)之後,\(x\)\(x\)的右子樹的距離大於\(x\)的左子樹的距離時,為了維護性質二,我們要交換\(x\)的右子樹和左子樹。順便維護性質三,所以直接\(dist_x = dist_{rson(x)}+1\).

inline int Merge(int x, int y){
    if (!x || !y) return x + y ; 
    if (S[x].val > S[y].val || (S[x].val == S[y].val && x > y)) swap(x, y) ;
    rs = Merge(rs, y) ; if (S[ls].dis < S[rs].dis) swap(ls, rs) ; 
    S[ls].rt = S[rs].rt = S[x].rt = x, S[x].dis = S[rs].dis + 1 ; return x ;
}

我們觀察,我們是不斷交替拆分右子樹,由推論可得我們的距離不會大於$\Theta(\log(n_x+1))+\Theta(\log(n_y+1))-2 =O(\log n_x+ \log n_y) $

這個地方比較喜聞樂見的是需要存\(root\),即需要路徑壓縮。不路徑壓縮的話,尋一次\(rt\)就是\(\Theta(n)\)的了,複雜度是不對的但似乎Luogu的模板,不路徑壓縮會更快

  • \(Pop\)

……\(pop\)的話,亂搞就好了\(233\)

inline void Pop(int x){ S[x].val = -1, S[ls].rt = ls, S[rs].rt = rs, S[x].rt = Merge(ls, rs) ; }

然後就是總程式碼:

#include <cstdio>
#include <iostream>

#define MAXN 150010
#define swap my_swap
#define ls S[x].Son[0]
#define rs S[x].Son[1]

using namespace std ;
struct Tree{
    int dis, val, Son[2], rt ;
}S[MAXN] ; int N, T, A, B, C, i ;

inline int Merge(int x, int y) ; 
int my_swap(int &x, int &y){ x ^= y ^= x ^= y ;}
inline int Get(int x){ return S[x].rt == x ? x : S[x].rt = Get(S[x].rt) ; }
inline void Pop(int x){ S[x].val = -1, S[ls].rt = ls, S[rs].rt = rs, S[x].rt = Merge(ls, rs) ; }
inline int Merge(int x, int y){
    if (!x || !y) return x + y ; if (S[x].val > S[y].val || (S[x].val == S[y].val && x > y)) swap(x, y) ;
    rs = Merge(rs, y) ; if (S[ls].dis < S[rs].dis) swap(ls, rs) ; S[ls].rt = S[rs].rt = S[x].rt = x, S[x].dis = S[rs].dis + 1 ; return x ;
}
int main(){
    cin >> N >> T ; S[0].dis = -1 ;
    for (i = 1 ; i <= N ; ++ i) 
        S[i].rt = i, scanf("%d", &S[i].val) ; 
    for (i = 1 ; i <= T ; ++ i){
        scanf("%d%d", &A, &B) ;
        if (A == 1){
            scanf("%d", &C) ;
            if (S[B].val == -1 || S[C].val == -1) continue ;
            int f1 = Get(B), f2 = Get(C) ; if (f1 != f2) S[f1].rt = S[f2].rt = Merge(f1, f2) ;
        }
        else {
            if(S[B].val == -1) printf("-1\n") ;
            else printf("%d\n", S[Get(B)].val), Pop(Get(B)) ;
        }
    }
    return 0 ;
}

\(\rm{0x03}\) 一點問題

問題大概就是路徑壓縮……

\(LuoguP3377\)很不負責任地處了資料,導致以下這份程式碼可以過:

using namespace std ;
struct Tree{
    int dis, val, F, Son[2] ;
}S[MAXN] ;
int N, T, A, B, C, i ;

inline int Merge(int x, int y) ; 
int my_swap(int &x, int &y){ x ^= y ^= x ^= y ;}
int Get(int x){ while(S[x].F) x = S[x].F ; return x ; }
inline void Pop(int x){ S[x].val = -1, S[ls].F = S[rs].F = 0, Merge(ls, rs) ; }
inline int Merge(int x, int y){
    if (!x || !y) return x + y ; if (S[x].val > S[y].val || (S[x].val == S[y].val && x > y)) swap(x, y) ;
    rs = Merge(rs, y), S[rs].F = x ; if (S[ls].dis < S[rs].dis) swap(ls, rs) ; S[x].dis = S[rs].dis + 1 ; return x ;
}
int main(){
    cin >> N >> T ; S[0].dis = -1 ;
    for (i = 1 ; i <= N ; ++ i) scanf("%d", &S[i].val) ; 
    for (i = 1 ; i <= T ; ++ i){
        scanf("%d%d", &A, &B) ;
        if (A == 1){
            scanf("%d", &C) ;
            if (S[B].val == -1 || S[C].val == -1 || B == C) continue ;
            int f1 = Get(B), f2 = Get(C) ; Merge(f1, f2) ;
        }
        else {
            if(S[B].val == -1) printf("-1\n") ;
            else printf("%d\n", S[Get(B)].val), Pop(Get(B)) ;
        }
    }
    return 0 ;
}

一切都很正常,但問題在於他複雜度不對:

int Get(int x){ while(S[x].F) x = S[x].F ; return x ; }

這顯然是個上界為\(O(n)\)的函式……不寒而慄……

所以他是不對的,這組資料可以很好的卡掉(由巨佬小粉兔製作)。

所以應該用一個並查集維護。而我們在路徑壓縮之後,必須要在\(pop\)後,給\(pop\)掉的點一個指標指向新的根,所以:


inline int Get(int x){ return S[x].rt == x ? x : S[x].rt = Get(S[x].rt) ; }
inline void Pop(int x){ S[x].val = -1, S[ls].rt = ls, S[rs].rt = rs, S[x].rt = Merge(ls, rs) ; }

於是最後的程式碼:

#include <cstdio>
#include <iostream>

#define MAXN 150010
#define swap my_swap
#define ls S[x].Son[0]
#define rs S[x].Son[1]

using namespace std ;
struct Tree{
    int dis, val, Son[2], rt ;
}S[MAXN] ; int N, T, A, B, C, i ;

inline int Merge(int x, int y) ; 
int my_swap(int &x, int &y){ x ^= y ^= x ^= y ;}
inline int Get(int x){ return S[x].rt == x ? x : S[x].rt = Get(S[x].rt) ; }
inline void Pop(int x){ S[x].val = -1, S[ls].rt = ls, S[rs].rt = rs, S[x].rt = Merge(ls, rs) ; }
inline int Merge(int x, int y){
    if (!x || !y) return x + y ; if (S[x].val > S[y].val || (S[x].val == S[y].val && x > y)) swap(x, y) ;
    rs = Merge(rs, y) ; if (S[ls].dis < S[rs].dis) swap(ls, rs) ; S[ls].rt = S[rs].rt = S[x].rt = x, S[x].dis = S[rs].dis + 1 ; return x ;
}
int main(){
    cin >> N >> T ; S[0].dis = -1 ;
    for (i = 1 ; i <= N ; ++ i) 
        S[i].rt = i, scanf("%d", &S[i].val) ; 
    for (i = 1 ; i <= T ; ++ i){
        scanf("%d%d", &A, &B) ;
        if (A == 1){
            scanf("%d", &C) ;
            if (S[B].val == -1 || S[C].val == -1) continue ;
            int f1 = Get(B), f2 = Get(C) ; if (f1 != f2) S[f1].rt = S[f2].rt = Merge(f1, f2) ;
        }
        else {
            if(S[B].val == -1) printf("-1\n") ;
            else printf("%d\n", S[Get(B)].val), Pop(Get(B)) ;
        }
    }
    return 0 ;
}

\(\rm{writter:Flower\_pks}\)

相關文章