左偏树学习

左偏树,是可并堆的一种。
顾名思义,左偏树是向左边偏一点。但也不全是(这个后面会解释)。我们先介绍一些概念及左偏树的几个性质。

外节点:

当一个的左儿子或右儿子为空时,这个节点就是外节点。举个例子,一条链上的所有点都是外节点。

距离:

一个点的距离,定义为它子树中离他最近的外节点到这个节点的距离(这与树的深度不同)
所以外节点的距离为0。下面有一张距离图,方便理解(摘自百度,蓝色的是距离)
这里写图片描述
空节点的距离为-1,因为我们可以把空节点理解成外节点的儿子。

左偏树的性质:
堆的性质:

既然左偏树是堆的一种实现,那么当然满足堆的性质。每个节点的儿子都比它大(或者小)。
所以如果左偏树成了棵满二叉树,那么它实际上就是一个堆。
valx<valson[x][l],valx<valson[x][r] v a l x < v a l s o n [ x ] [ l ] , v a l x < v a l s o n [ x ] [ r ]

左偏性:

对于任何点,它的左儿子的距离都大于等于右儿子
disson[x][l]>=disson[x][r] d i s s o n [ x ] [ l ] >= d i s s o n [ x ] [ r ]
这就能解释我们开始说的不全是向左边偏了,如果左儿子是一个点,而右儿子是一条很长的链,那也是满足要求的左偏树。
并且由于这个性质,我们可以得到一个推论
disx=disson[x][r]+1 d i s x = d i s s o n [ x ] [ r ] + 1
并且还有一个不太明显的推论:
n n 个点的左偏树,其最大距离为log(n+1)1
这个怎么证明呢?当根节距离为 d d 时,点最少的情况就是整棵树是满二叉树(因为其它情况下disson[x][l]>disd,左儿子点数肯定多于满二叉树的情况。)而满二叉树且距离为 d d 时,节点个数为2d+11,所以当有 n n 个点时,最大距离为log(n+1)1
这个性质可以保证左偏树的复杂度。

性质讲完了,就可以开始基本操作了(假设满足小根堆性质)

merge

左偏树最基础的操作,假设我们要合并以x,y为根的子树,那么假设 valx<valy v a l x < v a l y (不然swap一下)
那么我们接下去就合并 son[x][r] s o n [ x ] [ r ] y y 即可,这样递归下去,就可以完成合并。
但是这样合并之后可能会不满足左偏的性质,也就是disson[x][l]<disson[x][r],那么直接swap一下即可。

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

因为要删除的是根,所以直接将根删掉,让它左右儿子的父亲都变为0,然后 merge(son[x][l],son[x][r]) m e r g e ( s o n [ x ] [ l ] , s o n [ x ] [ r ] )

void pop(int x){
    fa[son[x][1]]=fa[son[x][0]]=0;
    merge(son[x][0],son[x][1]);
}
push

其实就是将1个点与整棵树merge

build

我们将所有点开成一个队列,然后每次取两个队头merge然后丢到队尾即可。

模板

以洛谷的板子题为例:
题目传送门

#include<bits/stdc++.h>
#define MAXN 100005
using namespace std;
int read(){
    char c;int x;while(c=getchar(),c<'0'||c>'9');x=c-'0';
    while(c=getchar(),c>='0'&&c<='9') x=x*10+c-'0';return x;
}
int n,m,val[MAXN],fa[MAXN],son[MAXN][2],dis[MAXN];
int getrt(int x){
    while(fa[x]) x=fa[x];
    return x;
}
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);
    son[x][1]=merge(son[x][1],y);
    fa[son[x][1]]=x;
    if(dis[son[x][1]]>dis[son[x][0]]) swap(son[x][1],son[x][0]);
    dis[x]=dis[son[x][1]]+1;
    return x;
}
void pop(int x){
    val[x]=-1;
    fa[son[x][1]]=fa[son[x][0]]=0;
    merge(son[x][0],son[x][1]);
}
int main()
{
    n=read();m=read();dis[0]=-1;
    for(int i=1;i<=n;i++) val[i]=read();
    for(int i=1;i<=m;i++){
        int type=read();
        if(type==1){
            int x=read(),y=read();
            if(val[x]==-1||val[y]==-1) continue;
            x=getrt(x);y=getrt(y);
            if(x==y) continue;
            merge(x,y);
        }
        else{
            int x=read();
            if(val[x]==-1){
                puts("-1");continue;
            }
            x=getrt(x);
            printf("%d\n",val[x]);
            pop(x);
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值