可并堆之左偏树浅谈

左偏树是用来快速地合并堆的

正常的堆是一颗完全二叉树,我们用笨方法去合并它:

假设我们要将x和y这两个小根堆合并,我们判断一下如果x的堆顶大于y的堆顶,就交换一下x和y,然后继续合并x的某个子孩子和y。

堆被人们所推广的原因就是因为它的时间复杂度比较稳定,根本原因是堆是一颗完全二叉树

但显然的:这样合并堆并没有保证时间复杂度,也就是说没有维护完全二叉树的形态;

这时候解决的办法之一便是利用左偏树;

它比普通的堆多了一个性质:向左偏;

注意,这里的向左偏并不是指子树的大小向左偏,而是最大深度向左偏;

为了方便我们理解,我们引入一下几种概念:

我们这里定义一个值,叫做"根值",一个节点的根值就是它到最近的叶子节点的距离;

我们保证,任意一个节点的左儿子的根值大于等于右儿子的根值;

这样我们会得到一个性质:
一个n个节点的左偏树距离最大为log(n+1)−1

简易论证:

  若左偏树的根值为一定值,则节点数最少的左偏树是完全二叉树

  若一棵左偏树的距离为k,则这棵左偏树至少有2^(k+1)-1个节点;

这样做的时间复杂度是O(logn),我们可以接受;(并且没用STL,常数也很小)

为了更加的优化程序,我们可以使用路径压缩来快速找到每个元素属于哪个根

#include <bits/stdc++.h>
#define inc(i,a,b) for(register int i=a;i<=b;i++)
using namespace std;
int n,m;
int dis[1000010],root[1000010],lson[1000010],rson[1000010];
template<class nT>
inline void read(nT& x)
{
	char c;while(c=getchar(),!isdigit(c));
	x=c^48;while(c=getchar(),isdigit(c)) x=x*10+c-48;
}
struct node{
	int pos;
	int value;
}tree[1000010];
int judge[10000010];
int find(int x)
{
	if(root[x]==x){
		return x;
	}
	return root[x]=find(root[x]);
}
int merge(int x,int y)
{
	if(!x||!y) return x+y;
	if(tree[x].value==tree[y].value){
		if(tree[y].pos<tree[x].pos){
			swap(x,y);
		}
	}
	else if(tree[y].value<tree[x].value){
		swap(x,y);
	}
	rson[x]=merge(rson[x],y);
	if(dis[lson[x]]<dis[rson[x]]) swap(lson[x],rson[x]);
	dis[x]=dis[rson[x]]+1;
	return x;
}
int main()
{
	dis[0]=-1;
	cin>>n>>m;
	inc(i,1,n){
		read(tree[i].value);
		root[i]=i; tree[i].pos=i;
	}
	inc(i,1,m){
		int type,x,y;
		read(type); read(x);
		if(type==1){
			read(y);
			if(judge[x]||judge[y]) continue;
			x=find(x); y=find(y);
			if(x==y) continue;
			root[x]=root[y]=merge(x,y);
		}
		else{
			if(judge[x]){
				cout<<"-1"<<endl;
			}
			else{
				x=find(x);
				cout<<tree[x].value<<endl;
				judge[x]=1;
				root[lson[x]]=root[rson[x]]=root[x]=merge(lson[x],rson[x]);
				lson[x]=rson[x]=dis[x]=0;
			}
		}
	}
}

原文地址:https://www.cnblogs.com/kamimxr/p/11779116.html

时间: 2024-10-08 23:05:57

可并堆之左偏树浅谈的相关文章

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

因为也是昨天刚接触左偏树,从头理解,如有不慎之处,跪请指教. 左偏树: 什 么是(fzy说)左偏树啊? 前置知识: 左偏树中dist:表示到右叶点(就是一直往右下找,最后一个)的距离,特别的,无右节点的为0. 堆:左偏树是个堆. 关于左偏性质:可以帮助堆合并(研究深了我也不懂的,看代码理解) 对于任意的节点,dist[leftson]>=dist[rightson],体现了左偏性质. 同理可得:对于任意右儿子的父亲节点的dist自然等于右儿子的dist+1喽 关于各种操作: merge: 是插入

zoj2334 Monkey King , 并查集,可并堆,左偏树

提交地址:点击打开链接 题意:  N(N<=10^5)只猴子,初始每只猴子为自己猴群的猴王,每只猴子有一个初始的力量值.这些猴子会有M次会面.每次两只猴子x,y会面,若x,y属于同一个猴群输出-1,否则将x,y所在猴群的猴王的力量值减半,然后合并这两个猴群.新猴群中力量值最高的为猴王.输出新猴王的力量值. 分析:涉及集合的查询,合并,取最值. 利用并查集和左偏树即可解决. #include <cstdio> #include <cstring> #include <io

【BZOJ-1455】罗马游戏 可并堆 (左偏树)

1455: 罗马游戏 Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 1355  Solved: 561[Submit][Status][Discuss] Description 罗马皇帝很喜欢玩杀人游戏. 他的军队里面有n个人,每个人都是一个独立的团.最近举行了一次平面几何测试,每个人都得到了一个分数. 皇帝很喜欢平面几何,他对那些得分很低的人嗤之以鼻.他决定玩这样一个游戏. 它可以发两种命令: 1. Merger(i, j).把i所在的团和j所在的

堆(左偏树)

左偏树 定义一个节点的高度为到叶子节点的最短距离. 一棵左偏树需要满足几个性质: \(1.\)它是一个堆. \(2.\)一个节点的左儿子的高度\(\ge\)右儿子的高度. \(3.\)一个节点的高度\(=\)右儿子的高度\(+1\). 由此可以得出一个节点数为\(n\)的左偏树的高度为\(\log (n+1)-1\). 每个节点需要维护左右儿子和权值. 实际上要维护的是左偏树森林,所以同时用并查集维护每个节点所属左偏树的根. 左偏树只有一个核心操作:merge. merge merge实现合并两

[BZOJ1455] 罗马游戏|左偏树

1455: 罗马游戏 Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 870  Solved: 347[Submit][Status][Discuss] Description 罗马皇帝很喜欢玩杀人游戏. 他的军队里面有n个人,每个人都是一个独立的团.最近举行了一次平面几何测试,每个人都得到了一个分数. 皇帝很喜欢平面几何,他对那些得分很低的人嗤之以鼻.他决定玩这样一个游戏. 它可以发两种命令: 1. Merger(i, j).把i所在的团和j所在的团

【转】左偏树(可合并优先队列)

[可并堆与左偏树] 我们最常用的二叉堆,是最常用的优先队列,它可以在O(logN)内实现插入和删除最小值操作.但是对于合并两个有序的优先队列,二叉堆就显得力不从心了. 左偏树是一种可并堆(Mergeable Heap),意思是可以在O(logN)时间内完成两个堆的合并操作.左偏树(Leftist Tree),或者叫左倾树,左式树,左式堆(Leftist Heap),左堆.顾名思义,它好象是向左偏的,实际上它是一种趋于非常不平衡的二叉树结构,但却能够实现对数级的合并时间复杂度. [左偏树的定义]

[BZOJ1455]罗马游戏-斜堆/左偏树-并查集(+数据生成器)

Problem 遗产 题目大意 罗马皇帝很喜欢玩杀人游戏. 他的军队里面有n个人,每个人都是一个独立的团.最近举行了一次平面几何测试,每个人都得到了一个分数. 皇帝很喜欢平面几何,他对那些得分很低的人嗤之以鼻.他决定玩这样一个游戏. 它可以发两种命令: 1. Merger(i, j).把i所在的团和j所在的团合并成一个团.如果i, j有一个人是死人,那么就忽略该命令. 2. Kill(i).把i所在的团里面得分最低的人杀死.如果i这个人已经死了,这条命令就忽略. 皇帝希望他每发布一条kill命令

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

题目描述 如题,一开始有N个小根堆,每个堆包含且仅包含一个数.接下来需要支持两种操作: 操作1: 1 x y 将第x个数和第y个数所在的小根堆合并(若第x或第y个数已经被删除或第x和第y个数在用一个堆内,则无视此操作) 操作2: 2 x 输出第x个数所在的堆最小数,并将其删除(若第x个数已经被删除,则输出-1并无视删除操作) 输入输出格式 输入格式: 第一行包含两个正整数N.M,分别表示一开始小根堆的个数和接下来操作的个数. 第二行包含N个正整数,其中第i个正整数表示第i个小根堆初始时包含且仅包

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

P3377 [模板]左偏树(可并堆) 题目描述 如题,一开始有N个小根堆,每个堆包含且仅包含一个数.接下来需要支持两种操作: 操作1: 1 x y 将第x个数和第y个数所在的小根堆合并(若第x或第y个数已经被删除或第x和第y个数在用一个堆内,则无视此操作) 操作2: 2 x 输出第x个数所在的堆最小数,并将其删除(若第x个数已经被删除,则输出-1并无视删除操作) 输入输出格式 输入格式: 第一行包含两个正整数N.M,分别表示一开始小根堆的个数和接下来操作的个数. 第二行包含N个正整数,其中第i个