堆是一种很常见的数据结构,普通的堆又称优先队列,用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; }