左偏树/可并堆 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;
}
posted @ 2025-05-25 00:05  LastKismet  阅读(27)  评论(0)    收藏  举报