【左偏树(可并堆)模板】

2 篇文章 0 订阅
1 篇文章 0 订阅

左偏树:

我想您应该会二叉堆吧,它包含三个操作,这里与小根堆为例:
1 查询最小值
2 弹出最小值
3 插入一个值
可以使用 STL priority _ queue 实现,也可以用pb_ds中的库实现,当然也可以手写反正我不会,笔者是用的系统堆(包含在库 algorithm 中),和手写堆速度相差无几,在此推荐给大家:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<string>
#include<map>
#include<set>
#include<vector>
#include<queue>
#include<stack>
#include<algorithm>
#define RG register
#define file(x) freopen(x".in","r",stdin);freopen(x".out","w",stdout);
using namespace std;

inline int gi(){
    int data=0,w=1;
    char ch=0;
    while(ch!='-'&&(ch>'9'||ch<'0')) ch=getchar();
    if(ch=='-') w=-1,ch=getchar();
    while(ch>='0'&&ch<='9') data=data*10+ch-'0',ch=getchar();
    return data*w;
}
inline bool cmp(const int &a,const int &b){return a>b;}//cmp存小根堆
struct heap{
    int a[510*510*16];int c;
    heap(){c=0;}
    inline void push(int x){c++;a[c]=x;push_heap(&a[1],&a[c+1],cmp);}
    inline int top(){return a[1];}
    inline void pop(){pop_heap(&a[1],&a[c+1],cmp);c--;}
    inline bool empty(){return c==0;}

}Q;
int main(){
    RG int n=gi();
    while(n--){
    int opt=gi();
    if(opt==1) Q.push(gi());
    if(opt==2) printf("%d\n",Q.top());
    if(opt==3) Q.pop();
    }
    return 0;
}

但是我要讲的并不是二叉堆,所以一笔带过(不懂的可以去网上搜,一点也不难)。

下面正式进入话题:

什么叫 呢?顾名思义,就是可以合并的堆,没错,可并堆就是可以合并的堆,它支持二叉堆的操作,而且还支持 log(n) 把两个堆合并。
此时, STL 就不行了( pb _ ds 还是可以滴)。所以我们要手写可并堆,而我们今天要讲的左偏树( LiftistTree ),就是其中的一种。
什么是左偏树咧?首先,我们又顾名思义一下,它很明显一棵树。没错!其实它就是一棵树,而且是颗二叉树。
它的节点上存 4 个值:左、右子树的地址,权值,距离。权值就是丢进去的数值。距离表示这个节点到它子树里面最近的叶子节点的距离。叶子节点距离为0
而既然它是一种特殊的数据结构,那自然有它自己的性质。下面介绍左偏树的 4 个性质,自己仔细想一想(还是以小根堆为例):
1节点的权值小于其左右儿子的权值
2 节点左儿子的距离不小于右儿子的距离(注意只是距离,不代表节点数和深度也是如此)
3 节点的距离等于右儿子的距离+ 1
4一个 n 个节点的左偏树距离最大为log(n+1)1
性质讲完了,所以——

下面是操作:

1 合并:
我们假设A的根节点小于等于B的根节点(否则交换A,B),把A的根节点作为新树C的根节点,剩下的事就是合并A的右子树和B了。合并了A的右子树和B之后,A的右子树的距离可能会变大,当A的右子树 的距离大于A的左子树的距离时,性质二会被破坏。在这种情况下,我们只须要交换A的右子树和左子树就能满足条件。
而且因为A的右子树的距离可能会变,所以要更新A的距离=右儿子距离+1。这样就合并完了。
代码:

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

2 删除,取出最小值
为什么放一起??
因为太容易了。。。
删除:把两颗子树一合并,就完了。
取出:找根节点即可。

inline void pop(int a){
    int x=getf(a);
    val[x]=-1;
    fa[ch[x][0]]=fa[ch[x][1]]=0;
    merge(ch[x][0],ch[x][1]);
}
inline int top(int a){return val[getf(a)];}

完整代码:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<algorithm>
#include<ctime>
#define RG register
#define file(x) freopen(x".in","r",stdin);
using namespace std;

inline int gi(){
    RG int data=0,w=1;
    RG char ch=0;
    while(ch!='-'&&(ch>'9'||ch<'0')) ch=getchar();
    if(ch=='-') w=-1,ch=getchar();
    while(ch>='0'&&ch<='9') data=(data<<1)+(data<<3)+(ch^48),ch=getchar();
    return w*data;
}
#define N 100010
class Liftist_Tree{
    public:
        int ch[N][2],val[N],dis[N],fa[N];
        Liftist_Tree(){memset(val,-1,sizeof(val));}
        int merge(int x,int y){
            if(x==0||y==0) return x+y;
            if(val[x]>val[y]||(val[x]==val[y]&&x>y)) swap(x,y);
            ch[x][1]=merge(ch[x][1],y);
            fa[ch[x][1]]=x;
            if(dis[ch[x][0]]<dis[ch[x][1]]) swap(ch[x][0],ch[x][1]);
            dis[x]=dis[ch[x][1]]+1;
            return x;
        }
        inline int top(int a){return val[getf(a)];}
        inline void pop(int a){
            int x=getf(a);
            val[x]=-1;
            fa[ch[x][0]]=fa[ch[x][1]]=0;
            merge(ch[x][0],ch[x][1]);
        }
        inline int getf(int x){
            while(fa[x]) x=fa[x];
            return x;
        }
}t; 
int main(){
    int n=gi();RG int m=gi();
    t.dis[0]=-1;
    for(RG int i=1;i<=n;i++) t.val[i]=gi();
    while(m--){
        int opt=gi();
        if(opt==1){
            int x=gi(),y=gi();
            if(t.val[x]==-1||t.val[y]==-1) continue;
            if(x==y) continue;
            int a=t.getf(x),b=t.getf(y);
            t.merge(a,b);
        }
        if(opt==2){
            int x=gi();
            if(t.val[x]==-1) puts("-1");
            printf("%d\n",t.top(x));
            t.pop(x);
        }
    }
    return 0;
}

Tips:评测地址

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值