其他分享
首页 > 其他分享> > 『左偏树』学习笔记

『左偏树』学习笔记

作者:互联网

背景

今天 LJ 莫名其妙的给我们讲了左偏树(其实只是给我们放了一个往期讲课视频).

讲的比较好的 blog .(其实是看了 blog 才懂的)


首先,我们必须明确,左偏树是一种 可以合并( merge )的 式数据结构. 其合并时间复杂度为 \(O(\log_2n).\)

定义

左偏树较之堆多了一个 \(dis\) 距离 .我们定义 \(dis_x\) 为子树中,节点 \(x\) 到最近的 外节点 (左儿子或右儿子是空结点的结点) 的距离. 特别地,空节点的距离为 \(-1\) .

为行文方便,现将文章中出现的英文及其对应意义列举如下(与左偏树无关)

\(val_x\) : 表示节点 \(x\) 对应的数值.

\(dis_x\) : 见距离定义.

\(ls_x\) : 节点 \(x\) 的左孩子.

\(rs_x\) : 节点 \(x\) 的右孩子.

\(rt_x\) : 节点 \(x\) 所在树的根节点

性质

堆性质 : 对于每个节点 \(x\) ,满足 \(val_x \le val_{ls_x},val_{rs_x}\) .

左偏性质 : 对于每个节点 \(x\) ,满足 \(dis_{ls_x}\le dis_{rs_x}\) .

基本结论

1.\(dis_x=dis_{rs_x}+1\) .

2
. 距离为 \(n\) 的左偏树至少有 \(2^{n+1}-1\) 个节点,此时该树为满二叉树.

3
.有 \(n\) 个节点的左偏树距离是 \(\log_2n\) .

基本操作:合并 merge

我们定义 merge(x,y) 为合并两棵以 \(x,y\) 为根节点的左偏树 . 返回合并后的根节点.

1.既然它要满足堆性质,那么 \(x,y\) 中小的肯定是合并后的根节点,方便起见这里我们默认 \(val_x\le val_y\),. 若不,则 swap(x,y) .

2.它还要满足左偏性质,那么我们不妨将 \(y\) 与 \(rs_x\) 合并(即 merge(rs[x],y) ),如果 \(dis_{ls_x}< dis_{rs_x}\),就 swap(ls[x],rs[y]) .

3.重复上述操作,直到 \(x,y\) 中有一个节点为空节点, 返回 \(x+y\) .

由于左偏树高 \(\log_2n\) ,所以合并的复杂度为 \(O(\log_2n)\) .

code:

int merge(int x,int y)
{
	if(!x||!y)	return x+y;
	if(t[y]<t[x])	swap(x,y);
	rs[x]=merge(rs[x],y);
	if(dis[ls[x]]<dis[rs[x]])	swap(ls[x],rs[x]);
	dis[x]=dis[rs[x]]+1;
	return x;
}

其他操作

插入

插入一个元素 \(x\) . 将其看做一棵树与原左偏树合并即可. 时间复杂度 \(O(\log_2 n)\) .

求最小值

因左偏堆满足堆性质,所以直接返回堆顶即可 . 时间复杂度 \(O(1)\) .

删除最小值

merge(ls[x],rs[x]) . 时间复杂度 \(O(\log_2 n)\) .

code:

inline void pop(int x)
{
	rt[ls[x]]=rt[rs[x]]=rt[x]=merge(ls[x],rs[x]);
	ls[x]=rs[x]=dis[x]=0;
}

给定一节点,求所在树的根节点

我们可以暴力,一层一层往上,但是有可能会出现刚好是一条链的情况。此时复杂度为 \(O(n)\) . zbc!

于是我们可以用 并查集 , 路径压缩 !

code:

int find(int x)
{
	return rt[x]==x?x:rt[x]=find(rt[x]);
}

如此一来,我们就需要维护 \(rt_x\) .

每次 merge 时, rt[x]=rt[y]=merge(x,y) .

每次 pop 时,rt[ls[x]]=rt[rs[x]]=rt[x]=merge(ls[x],rs[x]) .因为 \(x\) 有可能是某个 \(rt_i\) 的值,所以 \(rt_x\) 也要改变 .

删除任意已知节点

没有码过,就听 LJ 口胡了一下 .

先正常 pop , 然后一路更新 \(dis\) 就好了.

例题

P3377 【模板】左偏树(可并堆)

模板.

一次 A .

code:

#include<bits/stdc++.h>
#define FOR(i,j,k)  for(int i=(j);i<=(k);i++)
using namespace std;
int n,m;
int ls[100005],rs[100005],dis[100005],rt[100005];
bool del[100005];
struct point{
	int id,val;
	inline  bool operator <(point x) const{
		return val==x.val?id<x.id:val<x.val;
	}
}t[100005];
int find(int x)
{
	return rt[x]==x?x:rt[x]=find(rt[x]);
}
inline int read()
{
	int x=0,f=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
	return x*f;
}
int merge(int x,int y)
{
	if(!x||!y)	return x+y;
	if(t[y]<t[x])	swap(x,y);
	rs[x]=merge(rs[x],y);
	if(dis[ls[x]]<dis[rs[x]])	swap(ls[x],rs[x]);
	dis[x]=dis[rs[x]]+1;
	return x;
}
inline void pop(int x)
{
	rt[ls[x]]=rt[rs[x]]=rt[x]=merge(ls[x],rs[x]);
	ls[x]=rs[x]=dis[x]=0;
}
int main()
{
	dis[0]=-1;
	n=read(),m=read();
	for(int i=1;i<=n;i++)
		t[i].val=read(),rt[i]=i,t[i].id=i;
	while(m--)
	{
		int op=read(),x=read();
		if(op==1)
		{
			int y=read();
			if(del[x]||del[y])	continue;
			x=find(x),y=find(y);
			if(x!=y)	rt[x]=rt[y]=merge(x,y);
		}
		if(op==2)
		{
			if(del[x])
			{
				puts("-1");
				continue;
			}
			x=find(x);
			printf("%d\n",t[x].val);
			del[x]=true;
			pop(x);
		}
	}	
	return 0;
}

P2713 罗马游戏

code:

#include<bits/stdc++.h>
#define FOR(i,j,k)  for(int i=(j);i<=(k);i++)
using namespace std;
int n,m;
int ls[1000005],rs[1000005],rt[1000005],dis[1000005];
bool del[1000005];
struct point{
	int id,val;
	inline bool operator <(point x) const{
		return val==x.val?id<x.id:val<x.val;//注意,这里第一次写错了,写成了 id==x.id?val<x.val:if<x.id .
	}
}t[1000005];
int find(int x)
{
	return rt[x]==x?x:rt[x]=find(rt[x]);
}
inline int read()
{
	int x=0,f=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
	return x*f;
}
int merge(int x,int y)
{
	if(!x||!y)	return x+y;
	if(t[y]<t[x])	swap(x,y);
	rs[x]=merge(rs[x],y);
	if(dis[ls[x]]<dis[rs[x]])	swap(ls[x],rs[x]);
	dis[x]=dis[rs[x]]+1;
	return x;
}
inline void pop(int x)
{
	rt[ls[x]]=rt[rs[x]]=rt[x]=merge(ls[x],rs[x]);
	ls[x]=rs[x]=dis[x]=0;
}
int main()
{
	dis[0]=-1;
	n=read();
	for(int i=1;i<=n;i++)	t[i].val=read(),rt[i]=i,t[i].id=i;
	m=read();
	while(m--)
	{
		char op;
		cin>>op;
		if(op=='M')//merge
		{
			int x=read(),y=read();
			if(del[x]||del[y])	continue;
			x=find(x),y=find(y);
			if(x==y)	continue;
			rt[x]=rt[y]=merge(x,y);
		}
		if(op=='K')
		{
			int x=read();
			if(del[x])
			{
				puts("0");
				continue;
			}
			x=find(x);
			printf("%d\n",t[x].val);
			del[x]=true;
			pop(x);
		}
	}
	return 0;
}

P1456 Monkey King

很板.

无非就是小根堆变为大根堆.

无它.

#include<bits/stdc++.h>
#define FOR(i,j,k)  for(int i=(j);i<=(k);i++)
using namespace std;
int n,m;
int ls[100005],rs[100005],rt[100005],dis[100005];
struct point{
	int id,val;
	inline bool operator <(point x) const{
		return val==x.val?id<x.id:val<x.val;
	}
}t[100005];
int find(int x)
{
	return rt[x]==x?x:rt[x]=find(rt[x]);
}
int merge(int x,int y)
{
	if(!x||!y)	return x+y;
	if(t[x]<t[y])	swap(x,y);//大根堆
	rs[x]=merge(rs[x],y);
	if(dis[ls[x]]<dis[rs[x]])	swap(ls[x],rs[x]);
	dis[x]=dis[rs[x]]+1;
	return x; 
}
inline int pop(int x)
{
	int res=merge(ls[x],rs[x]);
	rt[ls[x]]=rt[rs[x]]=rt[x]=res;
	ls[x]=rs[x]=dis[x]=0;
	return res;
}
inline int read()
{
	int x=0,f=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
	return x*f;
}
int main()
{
	while(cin>>n)
	{
		dis[0]=-1;
		for(int i=1;i<=n;i++)
			t[i].val=read(),rt[i]=i,t[i].id=i,dis[i]=ls[i]=rs[i]=0;
		m=read();
		while(m--)
		{
			int x=read(),y=read();
			int p=find(x),q=find(y);
			if(p==q)
			{
				puts("-1");
				continue;
			}
			x=pop(p),y=pop(q);
			t[p].val>>=1,t[q].val>>=1;
			rt[x]=rt[p]=merge(p,x),rt[y]=rt[q]=merge(q,y);
			p=find(p),q=find(q);
			rt[p]=rt[q]=merge(p,q);
			printf("%d\n",t[rt[p]].val);
		}	
	}
	
	return 0;
}

\(\mathfrak{To}\) \(\mathfrak{Be}\) \(\mathfrak{Continued......}\)

标签:rt,rs,笔记,学习,merge,dis,节点,左偏
来源: https://www.cnblogs.com/LJC001151/p/14396158.html