左偏树 学习笔记

左偏树

左偏树到底是什么呢??? 左偏树实际上是可合并的堆。

他的节点不仅存了他的权值,还存了一个比较重要的信息 \(dis\)

dis 的定义: 一个节点到他的子树中的叶子节点的最近距离。

维护\(dis\)有什么用呢?

当我们合并两个堆时,可能会导致堆退化成一条链,这样我们的询问操作的复杂度就会变成 O(n)。

这显然不是我们想看到的。

而左偏树就是靠 \(dis\) 来维护左右两颗子树的平衡。

性质

  1. 节点的权值小于他儿子节点的取值。

  2. 左偏树的任意节点的左儿子的距离都要比右儿子大(不然他怎么叫左偏树呢雾

  3. 左偏树任意节点的距离等于右儿子的距离加一。

证明如下:

根据左偏性,可以得到他左儿子的距离要小于右儿子的距离,
而左偏树中距离的定义是一个节点到离其最近的外节点的距离,故为Dis(RightSon)+1。

  1. 一个n个节点的左偏树的距离最大为 log(n+1)-1(不需要掌握

证明如下:

若左偏树的距离为一定值,则结点数最少的左偏树是完全二叉树。

结点最少的话,就是左右儿子距离一样,这就是完全二叉树了。

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

距离为k的完全二叉树高度也是k,节点数就是 \(2^{k+1}-1\) 个。

这样就可以证明性质四了。

因为 n>= \(2^{k+1}-1\),所以 k<=log(n+1)-1。

操作

一 合并操作

首先,当我们要合并堆时,让要保持堆的性质,假设我们要维护小根堆,我们就要让根节点权值较小的放上边。

然后再让另一个树与他的右子树合并。

那么为什么要合并右子树呢???

因为左子树的距离是大于右子树的,合并左子树显然不优。

当我们合并完后,可能会出现这样一种情况,左子树的距离小于右子树,这时我们直接交换一下就行了。

代码

int merage(int x,int y)//返回根节点
{
	if(x == 0 || y == 0) return x + y;//如果有一个为空,直接返回,不用合并
	if(tr[x].val > tr[y].val) swap(x,y);//要满足堆的性质
	tr[x].rc = merage(tr[x].rc,y);//合并右子树和另外一棵树
	fa[tr[x].rc] = x;
	if(tr[tr[x].lc].dis < tr[tr[x].rc].dis) swap(tr[x].lc,tr[x].rc);//满足性质二
	tr[x].dis = tr[tr[x].rc].dis + 1;//满足性质三
	return x;
}

二 插入一个点

一个点就相当于一棵树,直接合并就行了。

代码同上。

三 删除根节点

我们要删根节点,等于把根节点孤立出来,这时候我们只需要合并左右两颗子树就行了

代码

void del(int x)
{
	tr[x].val = -1;//标记x已经被删除
	fa[tr[x].lc] = tr[x].lc; fa[tr[x].rc] = tr[x].rc;//先把左右儿子的父亲都设为他自己,方便以后合并
	fa[x] =  merage(tr[x].lc,tr[x].rc);//保持原有的父子关系不变
}

例题

P3377 模板左偏树

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 1e6+7;
int n,m,opt,x,y;
int fa[N],dis[N];
struct node{
	int val;
	int lc,rc;
	int fa,dis;
}tr[N];
int inline read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
int find(int x){
    if(tr[x].fa == x) return x;
    else return tr[x].fa = find(tr[x].fa);
}
int merage(int x,int y){
	if(x == 0 || y == 0) return x+y;
	if(tr[x].val > tr[y].val || (tr[x].val == tr[y].val && x > y)){
		swap(x,y);
	}
	tr[x].rc = merage(tr[x].rc,y);
	tr[tr[x].rc].fa = x;
	if(tr[tr[x].lc].dis < tr[tr[x].rc].dis) swap(tr[x].lc,tr[x].rc);
	tr[x].dis = tr[tr[x].rc].dis + 1;
	return x;
}
void del(int x){
	tr[x].val = -1;
	tr[tr[x].lc].fa = tr[x].lc, tr[tr[x].rc].fa = tr[x].rc;
	tr[x].fa = merage(tr[x].lc,tr[x].rc);
}
int main(){
	n = read(), m = read();
	for(int i = 1; i <= n; i++){
		tr[i].fa = i;
        tr[i].val = read();
	}
	while(m--){
		opt = read();
		if(opt == 1){
			x = read(); y = read();
			int fx = find(x) , fy = find(y);
			if(tr[x].val == -1 || tr[y].val == -1) continue;
			if(fx == fy) continue;
			tr[fx].fa = tr[fy].fa = merage(fx,fy);
		}
		else{
			x = read();
			if(tr[x].val == -1) cout<<-1<<endl;
			else{
				int y = find(x);
				printf("%d\n",tr[y].val);
				del(y);
			}
		}
	}
	return 0;
}

P2713 罗马游戏

左偏树的裸题,和模板差不多

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 1e6+10;
char opt;
int n,t,x,y,fa[N];
struct node{
	int lc,rc;
	int val,dis;
}tr[N];
inline int read()
{
	int s = 0, w = 1; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9'){s = s * 10+ch -'0'; ch = getchar();}
	return s * w;
}
int find(int x)
{
	if(fa[x] == x) return x;
	else return fa[x] = find(fa[x]);
}
int merage(int x,int y)
{
	if(x == 0 || y == 0) return x + y;
	if(tr[x].val > tr[y].val) swap(x,y);
	tr[x].rc = merage(tr[x].rc,y);
	fa[tr[x].rc] = x;
	if(tr[tr[x].lc].dis < tr[tr[x].rc].dis) swap(tr[x].lc,tr[x].rc);
	tr[x].dis = tr[tr[x].rc].dis + 1;
	return x;
}
void del(int x)
{
	tr[x].val = -1;
	fa[tr[x].lc] = tr[x].lc; fa[tr[x].rc] = tr[x].rc;
	fa[x] =  merage(tr[x].lc,tr[x].rc);
}
int main()
{
	n = read();
	for(int i = 1; i <= n; i++)
	{
		fa[i] = i;
		tr[i].val = read();
	}
	t = read();
	while(t--)
	{
		cin>>opt;
		if(opt == 'M')
		{
			x = read(); y = read();
			int xx = find(x), yy = find(y);
			if(tr[x].val == -1 || tr[y].val == -1) continue;
			if(xx == yy) continue;
			fa[xx] = fa[yy] = merage(xx,yy);
		}
		else if(opt == 'K')
		{
			x = read();
			int xx = find(x);
			if(tr[x].val == -1)
			{
				printf("%d\n",0);
				continue;
			}
			printf("%d\n",tr[xx].val);
			del(xx);
		}
	}
	return 0;
}

P1456 Monkey King

每次打架可以看成是删除两个堆的堆顶,插入一个数,合并两个堆。

左偏树随便维护一下就行。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 1e5+10;
int n,m,x,y,root,fa[N];
struct node
{
	int lc,rc,dis,val;
}tr[N];
inline int read()
{
    int s = 0, w = 1; char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
    return s * w;
}
int find(int x)
{
	if(fa[x] == x) return x;
	else return fa[x] = find(fa[x]);
}
int merage(int x,int y)
{
	if(!x || !y) return x + y; 
	if(tr[x].val < tr[y].val) swap(x,y);
	tr[x].rc = merage(tr[x].rc,y);
	fa[tr[x].rc] = x;
	if(tr[tr[x].rc].dis > tr[tr[x].lc].dis) swap(tr[x].lc,tr[x].rc);
	tr[x].dis = tr[tr[x].rc].dis + 1;
	return x;
}  
int del(int x)
{
	fa[tr[x].lc] = tr[x].lc, fa[tr[x].rc] = tr[x].rc;
	int root = merage(tr[x].lc,tr[x].rc);
	fa[x] = root;
	return root;
} 
int main()
{
	while(scanf("%d",&n) != EOF)
	{
		for(int i = 1; i <= n; i++) tr[i].lc = tr[i].rc = tr[i].dis = 0;
		for(int i = 1; i <= n; i++) fa[i] = i, tr[i].val = read();
		m = read();
		for(int i = 1; i <= m; i++)
		{
			x = read(); y = read();
			int fx = find(x), fy = find(y);
			if(fx == fy) printf("%d\n",-1);
			else
			{
				root = del(fx);
				tr[fx].lc = tr[fx].rc = 0; fa[fx] = fx; tr[fx].val /= 2;
				fx = merage(fx,root);
				root = del(fy);
				tr[fy].lc = tr[fy].rc = 0; fa[fy] = fy; tr[fy].val /= 2;
				fy = merage(fy,root);
				fx = merage(fx,fy);
				printf("%d\n",tr[fx].val);
			} 
		}
	}
}

ENDING

posted @ 2020-07-29 11:29  genshy  阅读(164)  评论(0编辑  收藏  举报