左偏树/可合并堆

左偏树/可合并堆 代码笔记

代码思路

主体部分:

合并堆(即merge函数)

大堆左偏,把小堆和大堆的右儿子合并。
感性理解:堆的形态将比较平衡。

辅助部分:

并查集维护堆关系

简化部分:

自定义数据类型(struct Bheap)

注意事项:

  1. 堆的最大数量是\(n+m\)

  2. 注意考虑堆被删空等细节情况(尤其是题目有要求时)

原理(极简)

\(+\)左偏 \(\to\) 距离小于\(\log_2{(n+1)}\)

代码注释

#include <cstdio>
#include <algorithm>
using namespace std;

const int N = 2000006;
int n, cnt, fa[N]; // 堆的数量 
struct Bheap{
    int l, r, w, h; // 左儿子 右儿子 堆顶 堆高 
}heap[N]; // 堆 
bool del[N];

int merge(int a, int b){ // 合并堆 
    if(heap[a].w < heap[b].w) swap(a, b); // a.w >= b.w
    if(!a || !b) return a|b; // 边界情况
	// ↑不讨论的话会发生越界导致RE或者MLE 
    int merg = merge(heap[a].r, b); // 递归
    heap[a].r = merg; fa[heap[a].r] = a; // 更新右儿子 
    if(heap[heap[a].l].h < heap[heap[a].r].h)
        swap(heap[a].l, heap[a].r); // 更新左偏
    heap[a].h = heap[heap[a].r].h + 1; // 更新堆高 
    return a;
}

int find(int a){ // 路径压缩的并查集 
    if(a != fa[a]) fa[a] = find(fa[a]);
    return fa[a];
}

int main(){
    scanf("%d", &n);
    int k, x, y;
    for(int i = 1; i <= n; i++){
        scanf("%d", &k);
        if(k == 1){ // 新增堆 
            scanf("%d", &x); // x:v
            heap[++cnt] = (Bheap){0, 0, x, 1};
            fa[cnt] = cnt; del[cnt] = 0; continue;
        }
        if(k == 2){ // 合并堆 
            scanf("%d%d", &x, &y);
            if(del[x] && del[y]){ printf("-1\n"); continue; }
            if(del[x]){ printf("%d\n", heap[find(y)].w); continue; }
            if(del[y]){ printf("%d\n", heap[find(x)].w); continue; }
            // ↑有堆被删空的情况 ↓合并并输出新堆顶 
            x = find(x), y = find(y);
            if(x != y) fa[x] = fa[y] = merge(x, y);
            printf("%d\n", heap[fa[x]].w);
            continue;
        }
        if(k == 3){ // 删除堆顶 
            scanf("%d", &x);
            if(del[x]){ // 如果删空 
                printf("-1\n");
                continue;
            }
            x = find(x), del[x] = 1; // 删除堆顶 
            if(heap[x].l){ // 如果堆里还有元素 
                int l = heap[x].l, r = heap[x].r;
                heap[x].l = heap[x].r = 0;
                // ↑删除堆顶 ↓合并左右儿子为新堆 
                fa[x] = fa[l] = fa[r] = merge(l, r);
                printf("%d\n", heap[fa[l]].w);
                // ↑输出新堆顶 
				continue;
            } // ↓如果删空 
            printf("-1\n");
        }
    }
    return 0;
}
posted @ 2023-12-01 11:45  _kilo-meteor  阅读(27)  评论(0)    收藏  举报