随机堆

一、定义

随机堆是一种时间复杂度与左偏树相近,代码巨短的可并堆。随机数完胜 dis 数组。


二、一些基本操作

  1. 合并 \(x\)\(y\)
int merge(int x,int y){
    if(!x||!y) return x|y;
    if(val[x]>val[y]) swap(x,y);
    if(rnd()&1) swap(ls[x],rs[x]);
    int t=merge(rs[x],y);rs[x]=t,fa[t]=x;
    return x;
}

各有 \(50\%\) 概率走左边或右边。

  1. \(x\) 中删除 \(id\)
void del(int x,int id){
    int t=merge(ls[id],rs[id]);
    if(rt[x]==id) rt[x]=t;
    else{
        fa[t]=fa[id];
        if(ls[fa[id]]==id) ls[fa[id]]=t;
        if(rs[fa[id]]==id) rs[fa[id]]=t;
    }
    ls[id]=rs[id]=fa[id]=0;
}

\(id\) 为根和非根的情况。


三. 时间复杂度证明。

期望随机路径长度为 \(E(h(T))\)。令 \(L,R\)\(T\) 的左右子树。

\(n\) 表示节点数,那么有 \(n_T=n_L+n_R+1\)

假设 \(E(h(t))\le O(\log(n+1))\)。归纳证明:

\[\begin{aligned} E(h(T))&=\frac{E(h(L))+E(h(R))}{2}+1\\ &\le \frac{\log(n_L+1)+\log(n_R+1)}{2}+1\\ &=\log 2\sqrt{(n_L+1)(n_R+1)}\\ &\le \frac{2\log((n_L+1)+(n_R+1))}{2}\\ &=\log(n_L+n_R+2)\\ &=O(n+1) \end{aligned} \]

归纳成立。


四、与其他堆的对比

堆类型 合并 删除代码长度(相对) 维护信息 优点
随机堆 期望 \(O(\log n)\) 期望 \(O(\log n)\) 极短 左右孩子 + 父指针
左偏树 最坏 \(O(\log n)\) 最坏 \(O(\log n)\) 较短 左右孩子 + dis
斐波那契堆 均摊 \(O(1)\) 均摊 \(O(\log n)\) 很长 复杂的多域标记

五、例题

  1. 洛谷 P11266【模板】可并堆 2

板子题。

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,m,rt[N],ls[N],rs[N],fa[N],val[N];
mt19937 rnd(time(0));
int merge(int x,int y){
    if(!x||!y) return x|y;
    if(val[x]>val[y]) swap(x,y);
    if(rnd()&1) swap(ls[x],rs[x]);
    int t=merge(rs[x],y);rs[x]=t,fa[t]=x;
    return x;
}
void del(int x,int id){
    int t=merge(ls[id],rs[id]);
    if(rt[x]==id) rt[x]=t;
    else{
        fa[t]=fa[id];
        if(ls[fa[id]]==id) ls[fa[id]]=t;
        if(rs[fa[id]]==id) rs[fa[id]]=t;
    }
    ls[id]=rs[id]=fa[id]=0;
}
void insert(int x,int id){rt[x]=merge(rt[x],id);}
int qval(int x){return val[x];}
int main(){
    srand(time(0));
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;++i) scanf("%d",val+i),rt[i]=i;
    while(m--){
        int op,x,y,z;
        scanf("%d",&op);
        if(op==0) scanf("%d %d",&x,&y),del(x,y);
        else if(op==1) scanf("%d",&x),printf("%d\n",qval(rt[x]));
        else if(op==2) scanf("%d %d",&x,&y),rt[x]=merge(rt[x],rt[y]),rt[y]=0;
        else if(op==3) scanf("%d %d %d",&x,&y,&z),del(x,y),val[y]=z,insert(x,y);
    }
}
--------------------------------------------------并不怎么美丽的分隔线--------------------------------------------------
  1. 洛谷 P1456 Monkey King

这里的 del 就不用考虑是否为根节点,因为都是根节点。

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,m;
int ls[N],rs[N],fa[N],val[N];
mt19937 rnd(time(0));
int merge(int x,int y){
    if(!x||!y) return x|y;
    if(val[x]<val[y]) swap(x,y);
    if(rnd()&1) swap(ls[x],rs[x]);
    int t=merge(rs[x],y);rs[x]=t,fa[t]=x;
    return x;
}
int del(int id){
    int t=merge(ls[id],rs[id]);
    if(t) fa[t]=0;
    ls[id]=rs[id]=fa[id]=0;
    return t;
}
int find(int x){while(fa[x]) x=fa[x];return x;}
int main(){
    while(~scanf("%d",&n)){
        for(int i=1;i<=n;i++) scanf("%d",val+i),ls[i]=rs[i]=fa[i]=0;
        scanf("%d",&m);
        while(m--){
            int x,y;scanf("%d %d",&x,&y);
            int fx=find(x),fy=find(y);
            if(fx==fy){puts("-1");continue;}
            int a=del(fx);val[fx]>>=1,a=merge(a,fx);
            int b=del(fy);val[fy]>>=1,b=merge(b,fy);
            printf("%d\n",val[merge(a,b)]);
        }
    }
    return 0;
}

六、总结

随机堆大法好~

posted @ 2026-07-24 20:11  yanzixuan2024  阅读(13)  评论(0)    收藏  举报