• 模板:左偏树


    如果你知道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
    ",tr[y].val);
            del(y);
            }
        }
        }
        return 0;
    }

     例题

    BZOJ2809:[Apio2012]dispatching:http://www.cnblogs.com/luyouqi233/p/7999445.html

    POJ3666:Making the Grade:http://www.cnblogs.com/luyouqi233/p/8005148.html

  • 相关阅读:
    QTextStream 居然接受FILE*这样的传统参数
    基于IOCP的高速文件传输代码
    tornado web框架
    Kaggle入门
    NET Core 介绍
    Wireshark
    设计和应用分布式调用跟踪系统
    Visual Studio Code和Docker开发asp.net core和mysql应用
    背单词
    多环境开发
  • 原文地址:https://www.cnblogs.com/luyouqi233/p/7994462.html
Copyright © 2020-2023  润新知