堆是一种很常见的数据结构,普通的堆又称优先队列,用c++的priority_queue可实现。

堆是一棵二叉树。它满足:

性质①:从上到下是单调不增或单调不减的。如果是小根堆,则每个节点的key均不大于它的左右儿子(如果存在的话)的key;大根堆则反之。

普通的堆支持查询堆顶元素[O(1)],删除堆顶元素[O(logn)],插入一个元素[O(logn)]三个操作。

现在需要维护合并操作,则普通的堆无能为力了。

于是引入左偏树。

左偏树首先是一个堆,即满足性质①,且对于每个节点,除了key,额外定义一个len值,即为它的子节点中最近的叶子节点到它的距离(如果左右儿子之一为空则len=0)。

对于左偏树,它还满足:

性质②:左儿子的len不小于右儿子len,或右儿子为空。从而一个节点的len要么为0(右儿子为空),要么为右儿子的len值+1。

左偏树其实只有一个操作——合并。(删除堆顶元素看作合并它的两个儿子,插入元素看作合并一棵左偏树和另一棵只有一个节点的左偏树)

合并操作如下:假设待处理的左偏树为小根堆。

首先固定堆顶元素较小的一棵树(设为A),插入另一棵树(设为B)。

规定插入过程中只把B中的元素插入在A中某个元素的右儿子处(当然要满足性质①),插入过程中左二子依然为原节点的左二子。

插入之后,回溯时,比较每个节点左右儿子len值大小,如果此时违背了性质②,则交换做右儿子即可。

模板:洛谷p3377

这个代码在洛谷上WA了一个点,死活找不到错误,调对了之后再更新QAQ

#include<iostream>
#include<cstdio>
#include<cstring>
#include<climits>
#include<cmath>
#include<algorithm>
using namespace std;
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define dep(i,a,b) for(int i=a;i>=b;i--)
const int M=100005;
int a[M],fa[M],ls[M]={0},rs[M]={0},len[M];
bool del[M]={0};

int Find(int x){return fa[x]==x?x:(fa[x]=Find(fa[x]));}
int merge(int x,int y){
    if(!x||!y)return x+y;
    if(a[x]>a[y])swap(x,y);
    rs[x]=merge(rs[x],y);
    if(!ls[x]||len[ls[x]]<len[rs[x]])swap(ls[x],rs[x]);
    if(!rs[x])len[x]=0;else len[x]=len[rs[x]]+1;
    return x;
}
    
int main(){int n,m;scanf("%d%d",&n,&m);
    rep(i,1,n){scanf("%d",&a[i]);fa[i]=i;}
    while(m--){
        int opt;scanf("%d",&opt);
        if(opt==1){
            int x,y;scanf("%d%d",&x,&y);
            if(del[x]||del[y])continue;
            int fx=Find(x),fy=Find(y);
            if(fx==fy)continue;
            int root=merge(fx,fy);
            fa[fx]=fa[fy]=root;
        }
        else{
            int x;scanf("%d",&x);
            if(del[x]){printf("-1\n");continue;}
            int fx=Find(x);
            printf("%d\n",a[fx]);
            del[fx]=1;
            int root=merge(ls[fx],rs[fx]);
            fa[fx]=fa[root]=root;
        }
    }
    return 0;
}