1. 程式人生 > >左偏樹(可並堆)

左偏樹(可並堆)

pow 們的 include lse fin \n def 兩個 printf

“左偏”樹?

左偏樹其實是一種可並堆,它可以 \(O(log_2 n)\) 合並兩個堆。

那左偏?也就是說他左邊肯定有什麽東西比右邊大……

別著急,在左偏樹上有一個叫距離的東西:

個點的距離,被定義為它子樹中離他最近的外節點到這個節點的距離(這與樹的深度不同)

其中我們定義一個節點為外節點,當且僅當這個節點的左子樹和右子樹中的一個是空節點。(註意外節點不是葉子節點)

技術分享圖片

這幅圖中的這三個節點都是外節點。

而左偏樹指的就是就是一個節點的左兒子的距離一定大於等於右兒子的距離。

例如下面就是一棵左偏樹(藍色數字表示每個節點的距離)

技術分享圖片

它有什麽特別的嗎?

一個合格的左偏樹,滿足下列性質:

  1. 左偏樹中任意一個節點的距離為其右兒子的距離\(+1\)


    \(dist_i=dist_{rson_i}+1\)(顯然)

  2. \(n\) 個點的左偏樹,距離最大為\(log(n+1)?1\)
    證明還是很簡單的,考慮左偏樹根節點的距離 \(d\) 為一定值,那麽節點數最少的情況就是一個完全二叉樹,節點數為\(2^{d+1}?1\)。那麽n個節點的左偏樹距離也就 \(≤log(n+1)?1\)

怎麽用它?

merge:

既然是可並堆肯定要合並啊!

現在有兩棵左偏樹,\(a,b\) 是他們的根節點,假設\(val_a≤val_b\)(否則 \(swap\) 一下\(a,b\)

既然\(val_a≤val_b\),說明如果將這兩棵樹合並,根應該還是\(a\)

這時,只需要遞歸合並\(rs_a,b\)這兩個點,知道右兒子為空,並將新樹的根節點作為\(rs_a\)

合並完成之後,\(dist_{rs_a}\)可能會變,為了保證左偏性質,如果\(dist_{ls_a}<dist_{rs_a}\),就交換 \(a\) 的左右兒子。

最後,更新 \(dist_a\),以 \(a\) 為合並後的樹的根節點。

技術分享圖片

代碼實現:
int merge(int x,int y){
    if(x==0||y==0)return x|y;
    if(v[x].val>v[y].val||(v[x].val==v[y].val&&x>y))
        swap(x,y);
    v[x].lch=merge(v[x].lch,y);
    if(v[v[x].lch].dis<v[v[x].rch].dis)
        swap(v[x].lch,v[x].rch);
    return v[x].dis=v[v[x].rch].dis+1,x;
}

這個合並和距離有關,所以是 \(O(log_2n)\)

push:

相當於與一個大小為1的堆合並。

pop:

相當於將根刪除,然後左右兒子合並。

CODE模板:

洛谷 P3377 【模板】左偏樹(可並堆)

#include<iostream>
#include<cstdio>
using namespace std;

#define lch ch[0]
#define rch ch[1]
int n,m,x,y,opt;
struct Leftist{
    int val,dis,fa,ch[2];
    friend int find(int x);
    friend int merge(int x,int y);
    inline int top();
    inline void pop();
}v[1000005];

int find(int x){
    while(v[x].fa)x=v[x].fa;
    return x;
}

int merge(int x,int y){
    if(x==0||y==0)return x|y;
    if(v[x].val>v[y].val||(v[x].val==v[y].val&&x>y))
        swap(x,y);
    v[x].lch=merge(v[x].lch,y);
    v[v[x].lch].fa=x;
    if(v[v[x].lch].dis<v[v[x].rch].dis)
        swap(v[x].lch,v[x].rch);
    return v[x].dis=v[v[x].rch].dis+1,x;
}

inline int Leftist::top(){return val;}

inline void Leftist::pop(){
    val=-1;
    v[lch].fa=v[rch].fa=0;
    merge(lch,rch);
}

int main(){
    scanf("%d%d",&n,&m);
    v[0].dis=-1;
    for(int i=1;i<=n;i++)scanf("%d",&v[i].val);
    for(int i=1;i<=m;i++){
        scanf("%d%d",&opt,&x);
        if(opt==1){
            scanf("%d",&y);
            if(~v[x].val&&~v[y].val&&x!=y){
                int fx=find(x),fy=find(y);
                merge(fx,fy);
            }
        }else{
            if(v[x].val==-1)puts("-1");
            else{
                int fx=find(x);
                printf("%d\n",v[fx].top());
                v[fx].pop();
            }
        }
    }
} 

左偏樹(可並堆)