模板:左偏树

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了模板:左偏树相关的知识,希望对你有一定的参考价值。

如果你知道priority_queue的话,那自然就知道左偏树的目的了。

左偏树的目的和优先队列一致,就是求出当前所在堆中的最大(小)值。

但是我们作为高贵的C++选手,我们为什么还要学习左偏树呢。

当然是因为priority_queue太!慢!了!

————————————————————————————————————

概念引入:

对于左偏树,我们引入两个概念:

外节点:如果该节点的左子树或右子树为空,那么该节点为外节点。

距离(dis):该节点到达最近的外节点经过的边的个数。

我们同时将优先队列的定义照搬过来:

键值(val):节点的权值。

同时将优先队列的性质照搬过来:

性质1:节点的键值小于或等于它的左右子节点的键值。

对于左偏树,赋予它一个性质:

性质2:节点的左子节点的距离不小于右子节点的距离。

同时可以推出:

性质3:节点的距离等于它的右子节点的距离加1。(显然)

为了满足性质3,我们规定空节点的dis为-1。

此外还有很多性质,不过都是跟推左偏树复杂度相关的性质,有兴趣可以百度。

——————————————————————

复杂度:

这里直接给出左偏树的复杂度:

合并:O(logN1+logN2)(N1和N2分别为待合并左偏树的节点个数)

插入:O(logn)

删除最小节点:O(logn)

建树:O(n)

删除已知编号节点:O(logn)

————————————————————

基本操作:

以建立大根堆为例,现将前三个操作说明。

合并:

  按照左偏树的定义合并即可。

  设要合并的两个堆的根节点为x,y。

  如果其中一个堆为空堆,则不需合并。

  否则比较它们的val,设x为val较小的堆根节点。

  则x显然是新堆(子堆)的根节点,但是y在哪里我们并不知道。

  为了满足左偏树的性质(不提供证明),我们将x的右子堆和y堆合并,最后按照它们的dis交换一下左右儿子即可。

插入:

  将插入元素看成堆的话,与合并相同。

删除最小节点:

  将根节点删除后,其左右子堆合并即可。

————————————————————————————————

学到这里代码实现不是很难,这里提供板子题目:

洛谷P3377:[模板]左偏树(可并堆):https://www.luogu.org/problemnew/show/3377

代码如下:

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<cctype>
#include<queue>
using namespace std;
const int N=1e5+3;
inline int read(){
    int X=0,w=0;char ch=0;
    while(!isdigit(ch)){w|=ch==-;ch=getchar();}
    while(isdigit(ch))X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
    return w?-X:X;
}
int fa[N];
struct tree{
    int l,r;
    int dis,val;
}tr[N];
int merge(int x,int y){//两个堆的根,返回新堆的根
    if(x==0||y==0)return x+y;//其中一个是空堆
    if(tr[x].val>tr[y].val||(tr[x].val==tr[y].val&&x>y))
    swap(x,y);//让x的价值比y的价值小,这样x就在y上面
    tr[x].r=merge(tr[x].r,y);//合并x右树和y
    fa[tr[x].r]=x;
    if(tr[tr[x].l].dis<tr[tr[x].r].dis)
    swap(tr[x].l,tr[x].r);//让右儿子更接近外节点
    tr[x].dis=tr[tr[x].r].dis+1;//根据右儿子dis更新节点dis
    return x;
}
int get(int x){
    while(fa[x])x=fa[x];
    return x;
}
void del(int x){
    tr[x].val=-1;
    fa[tr[x].l]=fa[tr[x].r]=0;
    merge(tr[x].l,tr[x].r);
    return;
}
int main(){
    int n=read();
    int m=read();
    for(int i=1;i<=n;i++)tr[i].val=read();
    for(int i=1;i<=m;i++){
    int c=read();
    if(c==1){
        int x=read();
        int y=read();
        if(tr[x].val==-1||tr[y].val==-1||x==y)continue;
        int nx=get(x);
        int ny=get(y);
        merge(nx,ny);
    }else{
        int x=read();
        if(tr[x].val==-1){
        puts("-1");
        }else{
        int y=get(x);
        printf("%d\n",tr[y].val);
        del(y);
        }
    }
    }
    return 0;
}

 

以上是关于模板:左偏树的主要内容,如果未能解决你的问题,请参考以下文章

模板左偏树

模板左偏树

模板左偏树

HDOJ 1512 几乎模板的左偏树

模板左偏树(可并堆)

[luogu3377]模板左偏树(可并堆)

(c)2006-2024 SYSTEM All Rights Reserved IT常识