zoukankan      html  css  js  c++  java
  • P3377 【模板】左偏树(可并堆)

    如题,一开始有 nn 个小根堆,每个堆包含且仅包含一个数。接下来需要支持两种操作:

    1. 1 x y:将第 xx 个数和第 yy 个数所在的小根堆合并(若第 xx 或第 yy 个数已经被删除或第 xx 和第 yy 个数在用一个堆内,则无视此操作)。

    2. 2 x:输出第 xx 个数所在的堆最小数,并将这个最小数删除(若有多个最小数,优先删除先输入的;若第 xx 个数已经被删除,则输出 -11 并无视删除操作)。

    左偏树byHsfzZH1

    左偏树是一种支持在O(logn*logn)的时间复杂度内进行合并的堆式数据结构

    一些定义:

    外节点:左儿子或右儿子是空节点的节点

    距离:一个节点的距离d[x]定义为其子树中与节点x最近的外节点到x的距离,特别的,定义空节点的距离为-1。

    左偏树的基本性质:

    具有堆的性质。

    具有左偏性质,即对于每个节点x,有d[l]>d[r]。

    基本结论:

    (1)节点x的距离d(x) = d(rc) + 1

    (2)距离为n的左偏树至少有2^(n+1)-1个节点,此时该左偏树的形态是一颗满二叉树。

    (3)有n个节点的左偏树的根节点的距离是O(logn)的。

    合并操作:

    左偏树最基本的操作是合并操作。

    定义merge(x,y)为合并两颗分别以xy为根节点的左偏树。其返回值为合并之后的根节点。

    首先不考虑左偏性质,我们描述一下合并两个具有堆性质的树的过程。假设我们要合并的是小根堆。

    (1)若vx<=vy,以x作为合并后的根节点,否则以y作为合并后的根节点。

    (2)将y与x的其中一个儿子合并,用合并后的根节点代替与y合并的儿子的位置,并返回x。

    (3)重复以上操作。如果x和y中有一个为空节点,返回x+y。

    令h为树高,每次合并都减少1,上述操作的时间复杂度是O(h)的。

    当要合并的树退化成一条链的时候,这样做的复杂度是O(n)的。

    要使时间复杂度更优,就要使树合并得更平衡。我们有两种方式:

    (1)每次随机选择x的左右儿子进行合并。(很像Treap)。

    (2)左偏树。

    由于左偏树中左儿子的距离大于右儿子的距离,我们每次将y与x的右儿子合并。

    由于左偏树的树高是logn的,所以单次合并的时间复杂度也是logn的。

    但是,两颗左偏树按照上述方法合并后,可能不再保持左偏树的左偏性质,在每次合并完后,判断对节点x是否有d(lc)>d(rc)。若没有,则交换lc,rc,并维护x的距离d(x) = d(rc) + 1。

    插入给定值:

    新建一个值等于插入值的节点,将该节点和左偏树合并即可。时间复杂度O(logn)。

    求最小值:

    由于左偏树的堆性质,左偏树上的最小值为其根节点的值。

    删除最小值:

    等价于删除左偏树的根节点,合并根节点的左右儿子即可。

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=1e5+100;
    int n,m,op,x,y;
    int lc[maxn];
    int rc[maxn];
    int d[maxn];
    int father[maxn];
    bool tf[maxn];
    struct Left_tree {
        int id,v;
        bool operator < (const Left_tree x) const {
            return v==x.v?id<x.id:v<x.v;
        }
    }a[maxn];
    int findfather (int x) {
        int a=x;
        while (x!=father[x]) x=father[x];
        while (a!=father[a]) {
            int z=a;
            a=father[a];
            father[z]=x;
        }
        return x;
    }
    int merge (int x,int y) {
        if (!x||!y) return x+y;
        if (a[y]<a[x]) swap(x,y);
        rc[x]=merge(rc[x],y);
        if (d[lc[x]]<d[rc[x]]) swap(lc[x],rc[x]);
        d[x]=d[rc[x]]+1;
        return x;
    }
    int main () {
        d[0]=-1;
        scanf("%d%d",&n,&m);
        for (int i=1;i<=n;i++) scanf("%d",&a[i].v),father[i]=i,a[i].id=i;
        for (int i=1;i<=m;i++) {
            scanf("%d%d",&op,&x);
            if (op==1) {
                scanf("%d",&y);
                if (tf[x]||tf[y]) continue;
                x=findfather(x);
                y=findfather(y);
                if (x!=y) father[x]=father[y]=merge(x,y);
            }
            if (op==2) {
                if (tf[x]) {
                    printf("-1
    ");
                    continue;
                }
                x=findfather(x);
                printf("%d
    ",a[x].v);
                tf[x]=1;
                father[lc[x]]=father[rc[x]]=father[x]=merge(lc[x],rc[x]);
                lc[x]=rc[x]=d[x]=0;
            }
        }
    }
  • 相关阅读:
    帧同步与状态同步的区别
    spread语法解析与使用
    CoordinatorLayout自定义Bahavior特效及其源码分析
    更便捷的Android多渠道打包方式
    用Dart&Henson玩转Activity跳转
    用RxJava处理复杂表单验证问题
    用RxJava处理嵌套请求
    技术与业务的抉择————论程序员的“瓶颈”问题
    Android Studio单元测试入门
    Android一键多渠道分发打包实战和解析
  • 原文地址:https://www.cnblogs.com/zhanglichen/p/13430939.html
Copyright © 2011-2022 走看看