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\n");
                continue;
            }
            x=findfather(x);
            printf("%d\n",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;
        }
    }
}

 

posted @ 2020-08-04 01:00  zlc0405  阅读(142)  评论(0编辑  收藏  举报