P3377 【模板】左偏树(可并堆)
如题,一开始有 nn 个小根堆,每个堆包含且仅包含一个数。接下来需要支持两种操作:
-
1 x y
:将第 xx 个数和第 yy 个数所在的小根堆合并(若第 xx 或第 yy 个数已经被删除或第 xx 和第 yy 个数在用一个堆内,则无视此操作)。 -
2 x
:输出第 xx 个数所在的堆最小数,并将这个最小数删除(若有多个最小数,优先删除先输入的;若第 xx 个数已经被删除,则输出 -1−1 并无视删除操作)。
左偏树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; } } }