随机堆
一、定义
随机堆是一种时间复杂度与左偏树相近,代码巨短的可并堆。随机数完胜 dis 数组。
二、一些基本操作
- 合并 \(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\%\) 概率走左边或右边。
- 在 \(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)\) | 很长 | 复杂的多域标记 |
五、例题
板子题。
#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);
}
}
--------------------------------------------------并不怎么美丽的分隔线--------------------------------------------------
这里的 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;
}
六、总结
随机堆大法好~

浙公网安备 33010602011771号