左偏树(可并堆)

模板:洛谷P3377 左偏树(可并堆)

可并堆嘛,顾名思义,就是它可以实现合并两个堆仅消耗$O(logn)$的时间复杂度。而普通的堆要消耗$O(n)$或$O(nlogn)$。

左偏树是一种优秀的可并堆,它同时具有堆和左偏的性质。其中左偏指从当前节点的左儿子出发到最近的一个“出口”的距离比从右儿子出发要长(或者相等)。

“出口”,就是可以到达的一颗空树啦,如果一个节点没有或只有一个儿子,那它就是一个“出口”。

为此我们为每个的维护一个离最近出口的距离值dis

显然,由于左偏性,一点的$dis$等于它右儿子的$dis+1$($tr[x].dis=tr[tr[x].rc].dis+1$)

左偏树最重要的操作就是merge,其用递归的方式实现合并。

假设我们要合并两个堆$X$,$Y$,节点$x$,$y$是他们的根节点,试图把$x$的右子树和$y$合并后,把这颗合并后的新堆作为$x$的右儿子。但是需要满足(小根)堆的性质,所以先保证$x$的权值小于$y$的权值。然后维护左偏性质,如果左子树的$dis$比右子树的小,那么两个儿子交换。最后上传$dis$给$x$

维护左偏性质可使合并的复杂度为$O(logn)$。

可以知道,合并的时间复杂度取决于$dis$(因为如果有“出口”,Merge()就直接返回单节点了)。因为合并时我们都选择右儿子与另一个堆进行合并,根据左偏性得知,左儿子的$dis$比右儿子的$dis$大。那么可以使用与启发式合并类似的证明方法得知,向下递归的次数最多不超过$O(logn)$次。

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<cstdio>
#include<queue>
#include<ctime>
#include<cstdlib>
using namespace std;
const int MXN=100005;

namespace LTr{//Leftist Tree,左偏树 
    struct Node{
        int fa,lc,rc,able;
        int dis,val;
    }tr[MXN];
    int n;
    void Create(int x,int val){tr[x]=(Node){0,0,0,1,0,val};}//新建节点 
    int Root(int x){//找根,但这种方式很暴力,可能被构造O(n)T掉,所以保险建议用并查集 
        if(tr[x].fa) return Root(tr[x].fa);
        else return x;
    }
    int Comb(int x,int y){//合并 
        if(!x||!y) return x+y;
        if(tr[x].val>tr[y].val//保证小根堆
            ||(tr[x].val==tr[y].val&&x>y)) swap(x,y);
        tr[x].rc=Comb(tr[x].rc,y);//递归合并 
        if(tr[x].rc) tr[tr[x].rc].fa=x;
        if(tr[tr[x].lc].dis<tr[tr[x].rc].dis)//保证左偏 
            swap(tr[x].lc,tr[x].rc);
        tr[x].dis=tr[tr[x].rc].dis+1;//更新dis 
        return x;
    }
    //For User
    void Init(int myn){n=myn;Create(0,0);}
    bool IsAble(int x){return tr[x].able;}//是否已被删除 
    void Merge(int x,int y){Comb(Root(x),Root(y));}
    int Top(int x){return tr[Root(x)].val;}//返回x所在堆的堆顶值 
    void Pop(int x){//弹出x所在堆的堆顶 
        x=Root(x);
        int lc=tr[x].lc,rc=tr[x].rc;
        tr[lc].fa=tr[rc].fa=0;//更新其儿子的父节点为空 
        tr[x]=(Node){0,0,0,0,0,0};//标记该点已经不存在 
        Merge(lc,rc);//合并左右节点 
    }
}
int n,qn;
int main(){
    cin>>n>>qn;
    LTr::Init(n);
    for(int i=1;i<=n;i++){
        int val;scanf("%d",&val);
        LTr::Create(i,val);
    }
    for(int i=1;i<=qn;i++){
        int type,x,y;scanf("%d",&type);
        switch(type){
            case 1:{
                scanf("%d%d",&x,&y);
                if(LTr::IsAble(x)&&LTr::IsAble(y)&&LTr::Root(x)!=LTr::Root(y))
                    LTr::Merge(x,y);
            break;}
            case 2:{
                scanf("%d",&x);
                if(LTr::IsAble(x)){
                    printf("%d
",LTr::Top(x));
                    LTr::Pop(x);
                }else printf("-1
");
            break;}
        }
    }
    return 0;
}
View Code(会被卡)
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<cstdio>
#include<queue>
#include<ctime>
#include<cstdlib>
using namespace std;
const int MXN=100005;

namespace LTr{//Leftist Tree,左偏树 
    struct Node{
        int jmp,lc,rc,able;
        int dis,val;
    }tr[MXN];
    int n;
    void Create(int x,int val){tr[x]=(Node){x,0,0,1,0,val};}//新建节点 
    int Root(int x){//并查集维护jmp找根
        if(tr[x].jmp==x) return tr[x].jmp;
        return tr[x].jmp=Root(tr[x].jmp);
    }
    int Comb(int x,int y){//合并 
        if(!x||!y) return x+y;
        if(tr[x].val>tr[y].val//保证小根堆
            ||(tr[x].val==tr[y].val&&x>y)) swap(x,y);
        tr[x].rc=Comb(tr[x].rc,y);//递归合并 
        if(tr[x].rc) tr[tr[x].rc].jmp=x; 
        if(tr[tr[x].lc].dis<tr[tr[x].rc].dis)//保证左偏 
            swap(tr[x].lc,tr[x].rc);
        tr[x].dis=tr[tr[x].rc].dis+1;//更新dis 
        return x;
    }
    //For User
    void Init(int myn){n=myn;Create(0,0);}
    bool IsAble(int x){return tr[x].able;}//是否已被删除 
    void Merge(int x,int y){Comb(Root(x),Root(y));}
    int Top(int x){return tr[Root(x)].val;}//返回x所在堆的堆顶值 
    void Pop(int x){//弹出x所在堆的堆顶 
        x=Root(x);
        int lc=tr[x].lc,rc=tr[x].rc;
        tr[lc].jmp=lc;tr[rc].jmp=rc;//一定要加,否则会在被删除的点和新根中反复横跳 
        tr[x]=(Node){Comb(lc,rc),0,0,0,0,0};
        //合并(注意用的Comb()而不是Merge())左右儿子,标记该点已经不存在,同时把jmp改到合并后的根上 
    }
}
int n,qn;
int main(){
    cin>>n>>qn;
    LTr::Init(n);
    for(int i=1;i<=n;i++){
        int val;scanf("%d",&val);
        LTr::Create(i,val);
    }
    for(int i=1;i<=qn;i++){
        int type,x,y;scanf("%d",&type);
        switch(type){
            case 1:{
                scanf("%d%d",&x,&y);
                if(LTr::IsAble(x)&&LTr::IsAble(y)&&LTr::Root(x)!=LTr::Root(y))
                    LTr::Merge(x,y);
            break;}
            case 2:{
                scanf("%d",&x);
                if(LTr::IsAble(x)){
                    printf("%d
",LTr::Top(x));
                    LTr::Pop(x);
                }else printf("-1
");
            break;}
        }
    }
    return 0;
}
View Code(并查集)
原文地址:https://www.cnblogs.com/sun123zxy/p/leftisttree.html