左偏树
左偏树
定义
左偏树(英语:leftist tree或leftist heap),也可称为左偏堆、左倾堆,是计算机科学中的一种树,是一种优先队列实现方式,属于可并堆,在信息学中十分常见,在统计问题、最值问题、模拟问题和贪心问题等等类型的题目中,左偏树都有着广泛的应用。斜堆是比左偏树更为一般的数据结构。
---------------百度百科
在这里我们引入一个外节点的概念:左子树或右子树为空的节点。
在引入一个距离的概念:一个节点到它最近的外节点所经过的距离。
性质
1.左偏树是一个堆,堆顶元素小于(大于)它左右儿子的元素;
2.左偏树左儿子的距离大于等于右儿子的距离;
3.节点的距离等于它右儿子的距离+1;
4.一颗有\(n\)个节点的左偏树,它的深度不超过\(\lfloor log_{2}(n+1) \rfloor - 1\)。
证明一下性质4,当左偏树是一颗满二叉树时,它的节点数有\(2^{k + 1} - 1\)个,所以\(n <= 2^{k + 1} - 1\),转化一下,\(k <= \lfloor log_2(n + 1) \rfloor - 1\)。
操作
合并
合并是左偏树最基础的操作,其他操作都要依赖合并操作。
先结合图片理解一下合并的过程:
我们将两个堆合并,将堆顶元素的大的堆(A)合并在堆顶元素小的堆(B)的下边(根据不同题意),如果A < B,交换就好了。我们每次将B与A的右子树合并,合并完如果发现左子树的距离小于右子树的距离,那么就交换左右子树,然后利用性质3更新B的距离。
根据性质4,合并的最差复杂度是\(O(log(sizeA) +log(sizeB))\)。
int merge(int x, int y) {
if(x == 0 || y == 0) return x + y; //如果有一棵树为空,直接返回那个不为空的树
if(vis[x] > vis[y] || (vis[x] == vis[y] || x > y)) swap(x, y); //保证堆顶最小,字典序小
ch[x][1] = merge(ch[x][1], y);
f[ch[x][1]] = x;
if(dis[ch[x][0]] < dis[ch[x][1]]) swap(ch[x][0], ch[x][1]); //保证左子树距离大于右子树距离
dis[x] = dis[ch[x][1]] + 1; //性质3
return x;
}
插入
就是将一个节点与一个堆合并。
删除
找到\(x\)的堆顶,将堆顶的左右子树合并。
int find(int x) {
if(fa[x]) x = fa[x]; return x;
}
void pop(int x) {
x = find(x);
f[ch[x][1]] = f[ch[x][0]] = 0;
merge(ch[x][1], ch[x][0]);
}
模板
这个题注意找堆顶时要写路径压缩,不然最后一个点过不去。因为\(find\)函数是\(O(n)\)的。
那怎么写路径压缩呢?路径压缩可能会改变左偏树的性质,我们在删点操作时,把删除的点连在合并完的堆下面,之后路径压缩找父亲时就不会断掉。
#include <cstdio>
#define R register
using namespace std;
inline int read() {
int s = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') { if(ch == '-') f = -1; ch = getchar(); }
while(ch >= '0' && ch <= '9') { s = (s << 1) + (s << 3) + (ch ^ 48); ch = getchar(); }
return s * f;
}
const int N = 1e5 + 5;
int n, m;
int vis[N], ch[N][2], f[N], dis[N], fa[N];
inline int find(R int x) {
while(f[x] == 0) return x;
return f[x] = find(f[x]);
}
void swap(int &x, int &y) { int t = x; x = y; y = t; }
int merge(R int x, R int y) {
if(x == 0 || y == 0) return x + y;
if(vis[x] > vis[y] || (vis[x] == vis[y] && x > y)) swap(x, y);
ch[x][1] = merge(ch[x][1], y);
f[ch[x][1]] = x;
if(dis[ch[x][0]] < dis[ch[x][1]]) swap(ch[x][1], ch[x][0]);
dis[x] = dis[ch[x][1]] + 1;
return x;
}
void div(R int x) {
vis[x] = -1;
f[ch[x][0]] = 0; f[ch[x][1]] = 0;
f[x] = merge(ch[x][1], ch[x][0]);
}
int main() {
n = read(); m = read(); dis[0] = -1;
for(int i = 1;i <= n; i++) vis[i] = read(), fa[i] = 0;
for(int i = 1;i <= m; i++) {
int opt = read();
if(opt == 1) {
int x = read(), y = read();
if(vis[x] == -1 || vis[y] == -1 || x == y) continue;
x = find(x), y = find(y);
x = merge(x, y);
}
else {
int x = read();
if(vis[x] == -1) printf("-1\n");
else {
x = find(x); printf("%d\n", vis[x]); div(x);
}
}
}
return 0;
}