定义
\qquad 左偏树是一棵二叉树,具有堆的性质,支持在 O ( log 2 n ) O(\log_2n) O(log2n)的时间复杂度内进行合并的数据结构
一些定义
外结点:左儿子或右儿子为空的结点
距离:一个结点的距离为该结点到最近的外结点的距离
\qquad
我们将空结点距离定义为
−
1
-1
−1
基本性质
左偏树具有堆性质,即对于每个结点
x
x
x,
v
x
<
v
l
c
v_x<v_{lc}
vx<vlc,
v
x
<
v
r
c
v_x<v_{rc}
vx<vrc
左偏树具有左偏性质,即对于每个节点
x
x
x,
d
i
s
t
l
c
≥
d
i
s
t
r
c
dist_{lc} \ge dist_{rc}
distlc≥distrc
结论
1. 结点
x
x
x的距离
d
i
s
t
x
=
d
i
s
t
r
c
+
1
dist_x=dist_{rc} + 1
distx=distrc+1
2. 距离为
n
n
n的左偏树至少有
2
n
+
1
−
1
2^{n + 1}-1
2n+1−1个结点。此时该左偏树的形态是一棵满二叉树
3. 结点数为
n
n
n的左偏树,其距离不超过
log
n
+
1
−
1
\log_{n + 1} - 1
logn+1−1
左偏树的操作
1. 合并
\qquad
合并两个左偏树
x
,
y
x,y
x,y,先考虑维护堆的性质
∙
\qquad\quad\bullet
∙
v
x
≤
v
y
v_x\leq v_y
vx≤vy则将
x
x
x作为新树的根
v
x
>
v
y
v_x>v_y
vx>vy则
s
w
a
p
(
v
x
,
v
y
)
swap(v_x,v_y)
swap(vx,vy)避免讨论
∙
\qquad\quad\bullet
∙将
y
y
y与
x
x
x任意一个儿子合并
∙
\qquad\quad\bullet
∙重复以上操作,直至
x
,
y
x,y
x,y中有一个为空结点
但是这样的合并操作可能会让合并的树退化为链,复杂度退化为
O
(
n
)
O(n)
O(n)为了让时间复杂度更优,就要让树变得更加平衡,有以下两种方法
\qquad\quad
1. 每次合并都随机与左右儿子合并
\qquad\quad
2. 左偏树
左偏树中左儿子的距离大于右儿子的距离,我们每次将
y
y
y与
x
x
x的右儿子合并,由于左偏树的树高是
log
2
n
\log_2n
log2n的,所以单次合并的时间复杂度也是
O
(
log
2
n
)
O(\log_2n)
O(log2n)\
维护左偏
每次合并后判断 d i s t l c < d i s t r c dist_{lc}<dist_{rc} distlc<distrc则交换 l c , r c lc,rc lc,rc,并维护 d i s t x = d i s t r c + 1 dist_x=dist_{rc} + 1 distx=distrc+1
int merge(int x,int y){
if(!x || !y) return x + y;
if(v[y] < v[x]) swap(v[x],v[y]);
rc[x] = merge(rc[x],y);
if(dist[lc[x]] < dist[rc[x]]) swap(lc[x],rc[x]);
dist[x] = dist[rc[x]] + 1;
}
2. 插入
\qquad
插入就是将新节点与左偏树合并
3. 删除
\qquad
删除就是合并目标节点左右儿子
4. 查询最小值
\qquad
左偏树具有堆的性质,最小值就是根节点
5. 查询某个结点所在左偏树的根节点
\qquad
和并查集很像,用一个数组
r
f
rf
rf来存储结点的父节点,用路径压缩来减少时间复杂度
int find(int x){
return rf[x] == x ? x : rf[x] = find(rf[x]);
}
附上完全代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 1e5 + 10;
struct Node{
int v,id;
bool operator <(Node x) const {return v == x.v ? id < x.id : v < x.v;}
}v[MAXN];
int rc[MAXN],lc[MAXN],rt[MAXN],tf[MAXN],dist[MAXN];
inline int read(){
int n = 0,l = 1;
char c = getchar();
while(c < '0' || c > '9'){
if(c == '-') l = -1;
c = getchar();
}
while(c >= '0' && c <= '9'){
n = (n << 1) + (n << 3) + (c & 15);
c = getchar();
}
return n * l;
}
int merge(int x,int y){
if(!x || !y) return x + y;
if(v[y] < v[x]) swap(x,y);
rc[x] = merge(rc[x],y);
if(dist[lc[x]] < dist[rc[x]]) swap(lc[x],rc[x]);
dist[x] = dist[rc[x]] + 1;
return x;
}
int find(int x){
return rt[x] == x ? x : rt[x] = find(rt[x]);
}
int main(){
dist[0] = -1;
memset(tf,0,sizeof(tf));
int n = read(),m = read();
for(int i = 1; i <= n; i ++){
v[i].v = read();
v[i].id = i;
rt[i] = i;
}
while(m --){
int op = read(),x = read();
if(op == 1){
int y = read();
if(tf[x] || tf[y]) continue;
x = find(x);
y = find(y);
if(x != y) rt[x] = rt[y] = merge(x,y);
}
else{
if(tf[x]){
printf("-1\n");
continue;
}
x = find(x);
printf("%d\n",v[x].v);
tf[x] = 1;
rt[lc[x]] = rt[rc[x]] = rt[x] = merge(lc[x],rc[x]);
lc[x] = rc[x] = dist[x] = 0;
}
}
return 0;
}