左偏树,是可并堆的一种。
顾名思义,左偏树是向左边偏一点。但也不全是(这个后面会解释)。我们先介绍一些概念及左偏树的几个性质。
外节点:
当一个的左儿子或右儿子为空时,这个节点就是外节点。举个例子,一条链上的所有点都是外节点。
距离:
一个点的距离,定义为它子树中离他最近的外节点到这个节点的距离(这与树的深度不同)
所以外节点的距离为0。下面有一张距离图,方便理解(摘自百度,蓝色的是距离)
空节点的距离为-1,因为我们可以把空节点理解成外节点的儿子。
左偏树的性质:
堆的性质:
既然左偏树是堆的一种实现,那么当然满足堆的性质。每个节点的儿子都比它大(或者小)。
所以如果左偏树成了棵满二叉树,那么它实际上就是一个堆。
valx<valson[x][l],valx<valson[x][r]
v
a
l
x
<
v
a
l
s
o
n
[
x
]
[
l
]
,
v
a
l
x
<
v
a
l
s
o
n
[
x
]
[
r
]
左偏性:
对于任何点,它的左儿子的距离都大于等于右儿子
disson[x][l]>=disson[x][r]
d
i
s
s
o
n
[
x
]
[
l
]
>=
d
i
s
s
o
n
[
x
]
[
r
]
这就能解释我们开始说的不全是向左边偏了,如果左儿子是一个点,而右儿子是一条很长的链,那也是满足要求的左偏树。
并且由于这个性质,我们可以得到一个推论
disx=disson[x][r]+1
d
i
s
x
=
d
i
s
s
o
n
[
x
]
[
r
]
+
1
并且还有一个不太明显的推论:
n
n
个点的左偏树,其最大距离为
这个怎么证明呢?当根节距离为
d
d
时,点最少的情况就是整棵树是满二叉树(因为其它情况下,左儿子点数肯定多于满二叉树的情况。)而满二叉树且距离为
d
d
时,节点个数为,所以当有
n
n
个点时,最大距离为
这个性质可以保证左偏树的复杂度。
性质讲完了,就可以开始基本操作了(假设满足小根堆性质)
merge
左偏树最基础的操作,假设我们要合并以x,y为根的子树,那么假设
valx<valy
v
a
l
x
<
v
a
l
y
(不然swap一下)
那么我们接下去就合并
son[x][r]
s
o
n
[
x
]
[
r
]
和
y
y
即可,这样递归下去,就可以完成合并。
但是这样合并之后可能会不满足左偏的性质,也就是,那么直接swap一下即可。
int merge(int x,int y){
if(x==0||y==0) return x+y;
if(val[x]>val[y]) swap(x,y);
son[x][1]=merge(son[x][1],y);
fa[son[x][1]]=x;
if(dis[son[x][1]]>dis[son[x][0]]) swap(son[x][1],son[x][0]);
dis[x]=dis[son[x][1]]+1;
return x;
}
pop
因为要删除的是根,所以直接将根删掉,让它左右儿子的父亲都变为0,然后 merge(son[x][l],son[x][r]) m e r g e ( s o n [ x ] [ l ] , s o n [ x ] [ r ] ) 。
void pop(int x){
fa[son[x][1]]=fa[son[x][0]]=0;
merge(son[x][0],son[x][1]);
}
push
其实就是将1个点与整棵树merge
build
我们将所有点开成一个队列,然后每次取两个队头merge然后丢到队尾即可。
模板
以洛谷的板子题为例:
题目传送门
#include<bits/stdc++.h>
#define MAXN 100005
using namespace std;
int read(){
char c;int x;while(c=getchar(),c<'0'||c>'9');x=c-'0';
while(c=getchar(),c>='0'&&c<='9') x=x*10+c-'0';return x;
}
int n,m,val[MAXN],fa[MAXN],son[MAXN][2],dis[MAXN];
int getrt(int x){
while(fa[x]) x=fa[x];
return x;
}
int merge(int x,int y){
if(x==0||y==0) return x+y;
if(val[x]>val[y]||(val[x]==val[y]&&x>y)) swap(x,y);
son[x][1]=merge(son[x][1],y);
fa[son[x][1]]=x;
if(dis[son[x][1]]>dis[son[x][0]]) swap(son[x][1],son[x][0]);
dis[x]=dis[son[x][1]]+1;
return x;
}
void pop(int x){
val[x]=-1;
fa[son[x][1]]=fa[son[x][0]]=0;
merge(son[x][0],son[x][1]);
}
int main()
{
n=read();m=read();dis[0]=-1;
for(int i=1;i<=n;i++) val[i]=read();
for(int i=1;i<=m;i++){
int type=read();
if(type==1){
int x=read(),y=read();
if(val[x]==-1||val[y]==-1) continue;
x=getrt(x);y=getrt(y);
if(x==y) continue;
merge(x,y);
}
else{
int x=read();
if(val[x]==-1){
puts("-1");continue;
}
x=getrt(x);
printf("%d\n",val[x]);
pop(x);
}
}
return 0;
}