左偏树是一种实现简单的可并堆,可以在logn的复杂度下面完成优先队列的插入,删除,合并操作。
这就是一颗左偏树啦 蓝色是该节点的dist
结构体定义
struct Node{
int l,r,d,v;//左右子树,距离和键值
Node(){
l=r=v=d=0;
}
}
我们定义左偏树的外界点为,一个左子树为空或者一个右子树为空的结点,结点的dist为它到它子树内外节点的最短距离。一个左偏树在满足堆的性质的前提下还应该满足它的右子树的dist不大于它的左子树的dist。由此,我们可以得出其他一些性质。
- 当前结点的dist为右子树dist的长度+1
- 若左偏树根节点的dist为一定值,则节点数最少的左偏树是一颗完全二叉树,并且为满二叉树
- 由上一条性质,可以知道根节点dist为n的左偏树,最少含有 2n+1−1 个结点
- 一个有n个结点的左偏树,根节点距离最大为 |log2(n+1)|−1
左偏树的操作(最大堆)
递归合并
如果有一颗树为空,返回另一棵树
否则,就将根节点键值较大树的右子树和根节点键值较小树合并
如果合并后破坏了左偏树的性质(左子树dist<右子树dist)
交换根节点的左右子树
更新根节点的dist值
int merge(int x,int y){
if(!x){
return y;
}
if(!y){
return x;
}
if(node[x].v<node[y].v){
swap(x,y);
}
node[x].r=merge(node[x].r,y);
if(node[node[x].l].d<node[node[x].r].d){
swap(node[x].l,node[x].r);
}
node[x].d=node[node[x].r].d+1;
return x;
}
插入
相当于和只有一个结点的树合并
int insert(int x,int v){
cnt++;
node[cnt].v=v;
return merge(x,cnt);//返回新的根节点
}
堆操作
int top(int x){
return node[x].v;
}
int pop(int x){
return merge(node[x].l,node[x].r);//返回新的根节点
}
以上操作都以左偏树堆顶的编号代表这一刻左偏树
例题 hdu1512 左偏树加并查集的模版题,今年省赛的热身赛就是这题来着,当时乱搞搞出来了(数据太弱),重新用左偏树写一下。
#include<iostream>
#include<cstring>
using namespace std;
const int maxn=1e5+7;
int cnt=0;
int n,m;
int p[maxn],pqroot[maxn];
struct Node{
int l,r,d,v;
Node(){
l=r=v=d=0;
}
void fight(){
l=r=d=0;
v/=2;
}
}node[maxn];
int merge(int x,int y){
if(!x){
return y;
}
if(!y){
return x;
}
if(node[x].v<node[y].v){
swap(x,y);
}
node[x].r=merge(node[x].r,y);
if(node[node[x].l].d<node[node[x].r].d){
swap(node[x].l,node[x].r);
}
node[x].d=node[node[x].r].d+1;
return x;
}
int pop(int x){
return merge(node[x].l,node[x].r);
}
int find(int x){
return x==p[x]?x:x=find(p[x]);
}
int uset(int x,int y,int root){
p[x]=y;
pqroot[y]=root;
}
int main(){
//freopen("in.txt","r",stdin);
while(cin>>n){
memset(node,0,sizeof(node));
memset(p,0,sizeof(p));
memset(pqroot,0,sizeof(pqroot));
node[0].d=-1;
for(int i=1;i<=n;i++){
p[i]=i;
pqroot[i]=i;
scanf("%d",&node[i].v);
}
cin>>m;
for(int i=0;i<m;i++){
int p1,p2;
scanf("%d%d",&p1,&p2);
int s1=find(p1),s2=find(p2);
if(s1!=s2){
int pqroot1=pqroot[s1],pqroot2=pqroot[s2];
int newroot1=pop(pqroot1);
int newroot2=pop(pqroot2);
node[pqroot1].fight();
node[pqroot2].fight();
newroot1=merge(newroot1,pqroot1);
newroot2=merge(newroot2,pqroot2);
int newpqroot=merge(newroot1,newroot2);
uset(s1,s2,newpqroot);
printf("%d\n",node[newpqroot].v);
}else{
printf("-1\n");
}
}
}
}
并查集维护一个当前集合内优先队列的根节点pqroot,合并的时候注意根的相关操作就OK了。