首页 > 其他分享 >左偏树

左偏树

时间:2023-06-11 19:23:08浏览次数:40  
标签:结点 rs 合并 左偏 节点 dis

左偏树

左偏树是一种可以让我们在 \(O(\log n )\) 的时间复杂度内进行合并的堆式数据结构。

为了方便以下的左偏树为小根堆来讨论。

定义

外结点:左儿子或者右儿子是空节点的结点。

距离:一个结点 \(x\) 的距离 \(dis[x]\) 定义为其子树中与结点 \(x\) 最近的外结点到 \(x\) 的距离。定义空节点的距离为 \(-1\)。

性质

  • 左偏树具有堆性质,即若满足小根堆的性质,则对于每一个结点 \(x\),都有 \(w[x]\le w[ls[x]],w[x]\le w[rs[x]]\)。其中 \(w\) 为权值,\(ls,rs\) 为左儿子,右儿子。

  • 左偏树具有左偏的性质,即对于每一个点 \(x\),都有 \(dis[ls[x]]\ge dis[rs[x]]\)。

基本结论

结点 \(x\) 的距离 \(dis[x]=dis[rs[x]]+1\),很明显上面说过的性质里面可以看出右儿子的距离更小,所以我们在计算当前结点的 \(dis\) 时应该用更小的 \(dis[rs]\)。

距离为 \(n\) 的左偏树至少有 \(2^{n+1}-1\) 个结点,此时该左偏树的形态是一棵满二叉树。

操作

合并

左偏树的很多操作都是需要用到合并操作的。

我们用 merge(x,y) 来表示合并两棵以 \(x,y\) 为根节点的左偏树,其返回值就是合并之后的根节点的编号。

首先如果要是不考虑左偏的性质,假设我们合并的是小根堆:

  1. 若 \(w[x]\le w[y]\),以 \(x\) 作为合并后的根节点;否则以 \(y\) 作为合并后的根节点。为了避免讨论,若有 \(w[x]>w[y]\) 我们就 swap 一下。

  2. 将 \(y\) 与 \(x\) 的其中一个儿子合并,用合并后的根节点代替与 \(y\) 合并的儿子的位置,并返回 \(x\)。

  3. 重复以上操作,如果 \(x,y\) 有一个是 \(0\),就返回 \(x+y\),也就是返回不为 \(0\) 的结点的编号。

当然上述的方法在数据为一条链的时候会 T 飞,所以我们需要让他左右保持一个相对平衡的状态,这个时候我们就有了左偏树(当然平衡树也可以)。

由于我们前面说过左偏树中左儿子的距离大于右儿子的距离,我们每次将 \(y\) 与 \(x\) 的右儿子合并,由于左偏树的树高为 \(\log n\) 的,所以单次合并的复杂度也为 \(O(\log n)\)。

但是,两棵左偏树按照上述方法合并后,可能不再保持左偏树的左偏性质。在每次合并完之后,判断对结点 \(x\) 是否有 \(dis[ls[x]]\ge dis[rs[x]]\),若没有则交换 \(ls,rs\),并维护 \(x\) 的距离 \(dis[x]=dis[rs[x]]+1\),即可维护左偏树的左偏性质。

插入给定值

我们可以直接新建一个点,然后将他和左偏树合并就好。

求最小(大)值

由于满足堆的性质,所以我们直接返回堆顶的元素就好。

删除最小(大)值

也就是删除堆顶元素,直接合并根节点的左右儿子即可。

删除任意结点

inline void del(int x)
{
	int tmp=merge(ls[x],rs[x]),fu=fa[x];
	f[tmp]=fa[tmp]=fu;
	f[x]=fa[x]=tmp;
	ls[fu]==u?ls[fu]=tmp:rs[fu]=tmp;
	while(fu)
	{
		if(dis[ls[fu]]<dis[rs[fu]])swap(ls[fu],rs[fu]);
		if(dis[fu]==dis[rs[fu]]+1)return ;
		dis[fu]=dis[rs[fu]]+1;
		fu=fa[fu];
	}
}

这里 \(fa\) 是父节点。

我们和删除根节点一样先合并子树,然后存起来删除的点的父节点 \(fu\)。

然后合并后的点的父节点和所属点先设为 \(fu\),然后我们把删除了的给调整成父节点和所在左偏树根节点为合并后的结点。

然后判断删除的点是父节点的左儿子还是右儿子然后用合并后的替换。

然后我们从父节点不断向上更新 \(dis\) 并用 swap 来维护左偏性质即可。

求给定结点所在的左偏树的根节点

我们可以开个数组 \(f\) 来记录父节点然后每次询问暴力跳,但是复杂度太高。

我们思考一下,我们可以像并查集一样采用路径压缩的办法来让这个复杂度变低。

在合并两个结点的时候,令 f[x]=f[y]=merge(x,y)

在删除左偏树中的最值时,我们令 f[ls[x]]=f[rs[x]]=f[x]=merge(x,y),因为 \(x\) 是之前左偏树的根节点,在路径压缩的时候可能有 \(f\) 的值等于 \(x\),所以 \(f[x]\) 也要指向删除后的根结点。

【模板】左偏树(可并堆) - 洛谷

code:


#include<bits/stdc++.h>
#define int long long
#define N 1000100
using namespace std;
int n,m,ls[N],rs[N],dis[N],f[N],vis[N];
struct sb{int id,w;bool operator<(sb x)const{return w==x.w?id<x.id:w<x.w;}}e[N];
inline int fid(int x){if(x==f[x])return x;return f[x]=fid(f[x]);}
inline int merge(int x,int y)
{
	if(!x||!y)return x+y;
	if(e[y]<e[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;
}
signed main()
{
	dis[0]=-1;
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	  cin>>e[i].w,f[i]=i,e[i].id=i;
	for(int i=1;i<=m;i++)
	{
		int op,x,y;
		cin>>op;
		if(op==1)
		{
			cin>>x>>y;
			if(vis[x]||vis[y])continue;
			int xx=fid(x),yy=fid(y);
			if(xx==yy)continue;
			f[xx]=f[yy]=merge(xx,yy);
		}
		else
		{
			cin>>x;
			if(vis[x]){puts("-1");continue;}
			int xx=fid(x);
			cout<<e[xx].w<<endl; 
			vis[xx]=1;
			f[ls[xx]]=f[rs[xx]]=f[xx]=merge(ls[xx],rs[xx]);
			ls[xx]=rs[xx]=dis[xx]=0;
		}
	}
	return 0;
}

参考:https://www.luogu.com.cn/blog/hsfzLZH1/solution-p3377

标签:结点,rs,合并,左偏,节点,dis
From: https://www.cnblogs.com/Multitree/p/17472692.html

相关文章

  • 【P4331 [BalticOI 2004]】Sequence 数字序列 题解(左偏树维护动态区间中位数)
    左偏树维护动态区间中位数。传送门P4331BalticOI2004Sequence数字序列。Solution1我的思路和题解前半部分完全重合了((如果按照单调不增去分割\(a\)序列的话,对于每一段我们能很简单地得出它的最佳答案:中位数。发现严格单调很难做,很难拿捏,考虑对\(a\)序列的每一项都进......
  • 左偏树学习笔记
    一、前言左偏树是一种可以在\(O(\logn)\)内快速合并的堆式数据结构。具体来说,插入一个元素:\(O(\logn)\)。查询最值:\(O(1)\)。删除最值:\(O(\logn)\)。合并:\(O(\logn)\)。减少一个元素的值:\(O(\logn)\)。同时它可以持久化。二、定义外节点:左儿子或者右儿子为空的......
  • 洛谷P1552 [APIO2012] 派遣 题解 左偏树
    题目链接:https://www.luogu.com.cn/problem/P1552题目大意:每次求子树中薪水和不超过\(M\)的最大节点数。解题思路:使用左偏树维护一个大根堆。首先定义一个Node的结构体:structNode{ints[2],c,sz,dis;longlongsum;Node(){};Node(int_c){s......
  • 洛谷 P3377 【模板】左偏树(可并堆)题解 左偏树模板题
    题目链接:https://www.luogu.com.cn/problem/P3377维护左偏树的同时还需要维护一个并查集。但是并查集也就一个find操作。pop的时候更新f[x]的操作很神奇。示例程序:#include<bits/stdc++.h>usingnamespacestd;constintmaxn=1e5+5;intn,m,op,x,y,val[m......
  • 【理论】左偏树笔记
    左偏树是可并堆的一种实现方法。左偏,很容易形象地理解它是什么意思。但对于一棵树,如何用形象和具体化的语言来描述左偏性质?考虑定义\(dis_i\)表示\(i\)的子树中最近......
  • 左偏树(可并堆)
    左偏树(可并堆)左偏树与配对堆一样,是一种可并堆,具有堆的性质,并且可以快速合并。一种\(O(\log_2n)\)内合并的数据结构。左偏树是一棵二叉树,它不仅具有堆的性质,并且是「左......
  • 左偏树
    参考:bilibili可并堆优先队列的缺点:无法高效合并两个堆左偏树节点中的数字是它的键值,而它是个堆,因此它满足堆的性质:节点的键值小于左右子节点的键值节点外蓝色数字叫......
  • 左偏树
    一.定义与性质1.外节点:一棵二叉树中左儿子或右儿子为空的节点称为外节点。2.左偏树(LeftistTree)是一种可并堆的实现。左偏树是一棵二叉树,每个节点维护的值有:左右儿......
  • 左偏树 学习笔记
    左偏树是一类拥有下列操作的数据结构:插入一个数\(O(logn)\)求最小值\(O(1)\)删除一个节点\(O(logn)\)合并两棵树\(O(logn)\)左偏树本身具有堆的性质......
  • [可并堆] 左偏树
    左偏树0x00绪言左偏树是一种比较神奇的数据结构,代码实现类似于线段树,但又是一种原理和线段树完全不一样的数据结构,如果读者打算阅读此博客,一定要读完,不要只看前半部分分......