左偏树/可并堆 Leftist Tree
更新日志
2025/05/25:开工。概念
左偏树是一棵满足左偏性质的堆。支持合并与删点、维护子树信息。
简要讲解左偏性质,记 \(dist\) 表示距离最近的右节点为空的节点的距离,这棵树满足 \(dist_{lson}\ge dist_{rson}\)。特别地,令空节点 \(dist\) 为 \(-1\)。
至于合并,首先确定谁是父节点(也就是维护堆的单调性),然后把另一个点与这个确定的父节点的右子树合并。合并完成之后维护左偏性质,看是否需要交换左右节点以及更新父节点的 \(dist\) 信息。
这样合并为什么对?在左偏树中,\(n\) 节点树根节点 \(dist\) 至多为 \(\log n\),此时这是一棵满二叉树。因此单次合并复杂度为 \(O(\log n)\)。
通常情况下找当前根节点会很有用,然后实现它,维护父节点信息即可,考虑路径压缩。
然后考虑删点操作,合并其两个子节点并更新父节点信息即可。
模板
这里以维护小根堆为例。
struct lftt{
int rt[N];
int val[N];
int lson[N],rson[N],dist[N];
void init(){
dist[0]=-1;
rep(i,1,n)rt[i]=i;
}
int find(int x){
if(rt[x]!=x)rt[x]=find(rt[x]);
return rt[x];
}
int merge(int a,int b){
if(!a||!b)return a+b;
if(a<b)swap(a,b);
if(val[a]<val[b])swap(a,b);
rt[a]=b;
rson[b]=merge(rson[b],a);
if(dist[lson[b]]<dist[rson[b]])swap(lson[b],rson[b]);
dist[b]=dist[rson[b]]+1;
return b;
}
void delt(int x){
val[x]=INT_MAX;
rt[lson[x]]=rt[rson[x]]=rt[x]=merge(lson[x],rson[x]);
}
}lft;
例题
code
int n,m;
struct lftt{
int rt[N];
int val[N];
int lson[N],rson[N],dist[N];
void init(){
dist[0]=-1;
rep(i,1,n)rt[i]=i;
}
int find(int x){
if(rt[x]!=x)rt[x]=find(rt[x]);
return rt[x];
}
int merge(int a,int b){
if(!a||!b)return a+b;
if(a<b)swap(a,b);
if(val[a]<val[b])swap(a,b);
rt[a]=b;
rson[b]=merge(rson[b],a);
if(dist[lson[b]]<dist[rson[b]])swap(lson[b],rson[b]);
dist[b]=dist[rson[b]]+1;
return b;
}
void delt(int x){
val[x]=INT_MAX;
rt[lson[x]]=rt[rson[x]]=rt[x]=merge(lson[x],rson[x]);
}
}lft;
bool dlt[N];
int main(){
read(n),read(m);
lft.init();
rep(i,1,n)lft.val[i]=read();
rep(i,1,m){
int op=read();
if(op==1){
int x=read(),y=read();
if(dlt[x]||dlt[y])continue;
x=lft.find(x),y=lft.find(y);
if(x==y)continue;
lft.merge(x,y);
}else{
int x=read();
if(dlt[x]){write(-1,'\n');continue;}
x=lft.find(x);
write(lft.val[x],'\n');
lft.delt(x);dlt[x]=1;
}
}
return 0;
}

浙公网安备 33010602011771号