可并堆(左偏树)简单学习

通常我们使用的二叉堆,是用一个数组实现的完全二叉树,对于查询有O(1)复杂度,对于插入、删除有O(logn)复杂度

而且常数比较小,是一个比较优秀的数据结构


但是。

当我们需要合并两个堆的时候,用普通的二叉堆就显得吃力了,时间复杂度达到O(mlogn)

这个时候可并堆这种数据结构就出现了。而在其中,尤以左偏树代码复杂度和时间复杂度综合得比较优秀


左偏树

在介绍左偏树之前,得先引入一个叫距离(d)的概念:
在左偏树中,一个节点u的距离,等于u到它和叶子节点中最近的左右儿子不满的节点【也就是可插入的节点】的距离。

然后左偏树是这样定义的:
左偏树是左儿子距离不小于右儿子距离的二叉堆。

这样子有什么用呢?
当插入节点时,只需往右儿子插入,可降低插入的时间复杂度。

实现

首先每个节点这样声明:
struct LT{
	int v,l,r,d;
	LT() {l=r=d=0;}
}e[maxn];



左偏树的所有操作都是以合并操作为基础的:
对于根节点为u和v两个左偏树是这样合并的:
1、如果其中有空节点,直接返回另一个非空结点
2、选择节点值较小的作为根节点,然后合并根节点的右儿子和另一个节点【merge(root.r,v)】【递归】
3、维护根节点的距离。我们设空节点的距离为-1,那么根节点的距离=右儿子距离+1

C++代码:

int merge(int a,int b){
	if(!a) return b;
	if(!b) return a;
	if(e[b].v<e[a].v) a^=b^=a^=b;
	e[a].r=merge(e[a].r,b);
	if(e[e[a].l].d<e[e[a].r].d) swap(e[a].l,e[a].r);
	if(!e[a].r) e[a].d=0;
	else e[a].d=e[e[a].r].d+1;
	return a;
}

有了merge操作其他一切操作就都特别简单了:
insert操作:先将建立一个新节点,再将新节点看做一棵树与原树合并。
del操作:合并根节点的左右子树

void insert(int x){
	int b=++siz;
	e[b].v=x;
	if(!rt) rt=b;
	else rt=merge(rt,b);
}

void del(){rt=merge(e[rt].l,e[rt].r);}



左偏树其实甚至可以当普通的二叉堆使用,只不过常数大一些,由于编程难度不高,但有时用的甚至可以成为替代二叉堆的一个选择。

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

一道板题
但用了左偏树+并查集
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=100005,INF=2000000000;

inline int read(){
	int out=0,flag=1;char c=getchar();
	while(c<48||c>57) {if(c=='-') flag=-1;c=getchar();}
	while(c>=48&&c<=57) {out=out*10+c-48;c=getchar();}
	return out*flag;
}

int N,M;

struct LT{
	int v,l,r,d,f;
	LT() {l=r=d=f=0;}
}e[maxn];

int merge(int a,int b){
	if(!a) return b;
	if(!b) return a;
	if(e[a].v>e[b].v||(e[a].v==e[b].v&&a>b)) swap(a,b);
	e[a].r=merge(e[a].r,b);
	e[e[a].r].f=a;
	if(e[e[a].l].d<e[e[a].r].d) swap(e[a].l,e[a].r);
	e[a].d=e[e[a].r].d+1;
	return a;
}

void del(int r){
	e[e[r].l].f=e[e[r].r].f=0;
	merge(e[r].l,e[r].r);
	e[r].v=-1;
}

int find(int u){
	if(!e[u].v==-1) return 0;
	while(e[u].f) u=e[u].f;
	return u;
}

int main()
{
	e[0].d=-1;
	N=read();
	M=read();
	for(int i=1;i<=N;i++)
		e[i].v=read();
	int cmd,a,b,fa,fb;
	while(M--){
		cmd=read();
		if(cmd&1){
			a=read();
			b=read();
			if(e[a].v==-1||e[b].v==-1) continue;
			fa=find(a);
			fb=find(b);
			if(fa!=fb) merge(fa,fb);
		}else{
			a=read();
			if(e[a].v==-1) printf("-1
");
			else{
				fa=find(a);
				printf("%d
",e[fa].v);
				del(fa);
			}
		}
	}
	return 0;
}

洛谷P1456 Monkey King

差不多的题,左偏树+并查集
【猴子找朋友来帮忙自己却不打= =】

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=100005,INF=2000000000;

inline int read(){
	int out=0,flag=1;char c=getchar();
	while(c<48||c>57) {if(c=='-') flag=-1;c=getchar();}
	while(c>=48&&c<=57) {out=out*10+c-48;c=getchar();}
	return out*flag;
}

int N,M;

struct LT{
	int v,l,r,d;
	LT() {l=r=d=0;}
}e[maxn];
int fa[maxn];

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

int merge(int u,int v){
	if(!u) return v;
	if(!v) return u;
	if(e[u].v<e[v].v) u^=v^=u^=v;
	e[u].r=merge(e[u].r,v);
	fa[e[u].r]=u;
	if(e[e[u].l].d<e[e[u].r].d) swap(e[u].l,e[u].r);
	e[u].d=e[e[u].r].d+1;
	return u;
}

int del(int u){
	int lc=e[u].l,lr=e[u].r;
	e[u].l=e[u].r=0;
	fa[lc]=lc;fa[lr]=lr;
	return merge(lc,lr);
}

int main()
{
	e[0].d=-1;
	int a,b,Fa,Fb;
	while(cin>>N){
		for(int i=1;i<=N;i++) {e[i].v=read();e[i].l=e[i].r=e[i].d=0;fa[i]=i;}
		M=read();
		while(M--){
			a=read();
			b=read();
			Fa=find(a);
			Fb=find(b);
			if(Fa!=Fb){
				a=del(Fa);
				b=del(Fb);
				e[Fa].v>>=1;
				e[Fb].v>>=1;
				a=merge(a,Fa);
				b=merge(b,Fb);
				a=merge(a,b);
				printf("%d
",e[a].v);
			
			}else printf("-1
");
		}
	}
	return 0;
}

洛谷 P1552 [APIO2012]派遣

这是一道左偏树+树上DP
用大根堆维护,将所有的儿子【包括儿子的子树】建立成堆,删去最大的直至工资在预期范围内,就可以得到以该点为管理者的最大收益。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long int
using namespace std;
const int maxn=100005,INF=2000000000;

inline int read(){
	int out=0,flag=1;char c=getchar();
	while(c<48||c>57) {if(c=='-') flag=-1;c=getchar();}
	while(c>=48&&c<=57) {out=out*10+c-48;c=getchar();}
	return out*flag;
}

int N,M,lson[maxn],rb[maxn],root;
LL dp[maxn],lead[maxn];

struct LT{
	LL v;
	int l,r,d;
	LT() {l=r=d=0;}
}e[maxn];

struct node{
	LL v;
	int cnt,rt;
};

int merge(int u,int v){
	if(!u) return v;
	if(!v) return u;
	if(e[u].v<e[v].v) u^=v^=u^=v;
	e[u].r=merge(e[u].r,v);
	if(e[e[u].l].d<e[e[u].r].d) swap(e[u].l,e[u].r);
	e[u].d=e[e[u].r].d+1;
	return u;
}

int del(int u) {return merge(e[u].l,e[u].r);}

node dfs(int u){
	if(!lson[u]){
		dp[u]=e[u].v<=M ? lead[u]:0;
		return e[u].v<=M ? (node){e[u].v,1,u}:(node){0,0,0};
	}else{
		int rt=u,cnt=1,v=lson[u];
		LL s=e[u].v;
		node t;
		while(v){
			t=dfs(v);
			cnt+=t.cnt;s+=t.v;rt=merge(rt,t.rt);
			v=rb[v];
		}
		while(s>M){
			s-=e[rt].v;
			rt=del(rt);
			cnt--;
		}
		dp[u]=(LL)lead[u]*(LL)cnt;
		return (node){s,cnt,rt};
	}
}

int main()
{
	e[0].d=-1;
	int f;
	N=read();
	M=read();
	for(int i=1;i<=N;i++){
		f=read();rb[i]=lson[f];lson[f]=i;
		if(!f) root=i;
		e[i].v=read();
		lead[i]=read();
	}
	dfs(root);
	LL ans=0;
	for(int i=1;i<=N;i++) ans=max(ans,dp[i]);
	cout<<ans<<endl;
	return 0;
}



原文地址:https://www.cnblogs.com/Mychael/p/8282896.html