zoukankan      html  css  js  c++  java
  • 左偏树(可并堆)

    模板:洛谷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(并查集)
  • 相关阅读:
    Java中常见数据结构:List与Map
    JAVA 双重检查锁定和延迟初始化
    Spring 读取配置文件(二)
    Spring 读取配置文件(一)
    Java配置文件读取和路径设置
    动态设置spring配置PropertyPlaceholderConfigurer location的路径
    MySQL 数据库备份种类以及经常使用备份工具汇总
    打印二叉树两个叶子节点间的路径
    读书报告之《改动代码的艺术》 (I)
    虚幻引擎自带的创建插件的插件
  • 原文地址:https://www.cnblogs.com/sun123zxy/p/leftisttree.html
Copyright © 2011-2022 走看看