左偏树:
我想您应该会二叉堆吧,它包含三个操作,这里与小根堆为例:
1、
查询最小值
2、
弹出最小值
3、
插入一个值
可以使用
STL
的
priority
_
queue
实现,也可以用pb_ds中的库实现,当然也可以手写反正我不会,笔者是用的系统堆(包含在库
algorithm
中),和手写堆速度相差无几,在此推荐给大家:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<string>
#include<map>
#include<set>
#include<vector>
#include<queue>
#include<stack>
#include<algorithm>
#define RG register
#define file(x) freopen(x".in","r",stdin);freopen(x".out","w",stdout);
using namespace std;
inline int gi(){
int data=0,w=1;
char ch=0;
while(ch!='-'&&(ch>'9'||ch<'0')) ch=getchar();
if(ch=='-') w=-1,ch=getchar();
while(ch>='0'&&ch<='9') data=data*10+ch-'0',ch=getchar();
return data*w;
}
inline bool cmp(const int &a,const int &b){return a>b;}//cmp存小根堆
struct heap{
int a[510*510*16];int c;
heap(){c=0;}
inline void push(int x){c++;a[c]=x;push_heap(&a[1],&a[c+1],cmp);}
inline int top(){return a[1];}
inline void pop(){pop_heap(&a[1],&a[c+1],cmp);c--;}
inline bool empty(){return c==0;}
}Q;
int main(){
RG int n=gi();
while(n--){
int opt=gi();
if(opt==1) Q.push(gi());
if(opt==2) printf("%d\n",Q.top());
if(opt==3) Q.pop();
}
return 0;
}
但是我要讲的并不是二叉堆,所以一笔带过(不懂的可以去网上搜,一点也不难)。
下面正式进入话题:
什么叫
可并堆
呢?顾名思义,就是可以合并的堆,没错,可并堆就是可以合并的堆,它支持二叉堆的操作,而且还支持
log(n)
把两个堆合并。
此时,
STL
就不行了(
pb
_
ds
还是可以滴)。所以我们要手写可并堆,而我们今天要讲的左偏树(
LiftistTree
),就是其中的一种。
什么是左偏树咧?首先,我们又顾名思义一下,它很明显一棵树。没错!其实它就是一棵树,而且是颗二叉树。
它的节点上存
4
个值:左、右子树的地址,权值,距离。权值就是丢进去的数值。距离表示这个节点到它子树里面最近的叶子节点的距离。叶子节点距离为
而既然它是一种特殊的数据结构,那自然有它自己的性质。下面介绍左偏树的
4
个性质,自己仔细想一想(还是以小根堆为例):
2、
节点左儿子的距离不小于右儿子的距离(注意只是距离,不代表节点数和深度也是如此)
3、
节点的距离等于右儿子的距离+
1
性质讲完了,所以——
下面是操作:
1、
合并:
我们假设A的根节点小于等于B的根节点(否则交换A,B),把A的根节点作为新树C的根节点,剩下的事就是合并A的右子树和B了。合并了A的右子树和B之后,A的右子树的距离可能会变大,当A的右子树 的距离大于A的左子树的距离时,性质二会被破坏。在这种情况下,我们只须要交换A的右子树和左子树就能满足条件。
而且因为A的右子树的距离可能会变,所以要更新A的距离=右儿子距离+1。这样就合并完了。
代码:
int merge(int x,int y){
if(x==0||y==0) return x+y;
if(val[x]>val[y]||(val[x]==val[y]&&x>y)) swap(x,y);
ch[x][1]=merge(ch[x][1],y);
fa[ch[x][1]]=x;
if(dis[ch[x][0]]<dis[ch[x][1]]) swap(ch[x][0],ch[x][1]);
dis[x]=dis[ch[x][1]]+1;
return x;
}
2、
删除,取出最小值
为什么放一起??
因为太容易了。。。
删除:把两颗子树一合并,就完了。
取出:找根节点即可。
inline void pop(int a){
int x=getf(a);
val[x]=-1;
fa[ch[x][0]]=fa[ch[x][1]]=0;
merge(ch[x][0],ch[x][1]);
}
inline int top(int a){return val[getf(a)];}
完整代码:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<algorithm>
#include<ctime>
#define RG register
#define file(x) freopen(x".in","r",stdin);
using namespace std;
inline int gi(){
RG int data=0,w=1;
RG char ch=0;
while(ch!='-'&&(ch>'9'||ch<'0')) ch=getchar();
if(ch=='-') w=-1,ch=getchar();
while(ch>='0'&&ch<='9') data=(data<<1)+(data<<3)+(ch^48),ch=getchar();
return w*data;
}
#define N 100010
class Liftist_Tree{
public:
int ch[N][2],val[N],dis[N],fa[N];
Liftist_Tree(){memset(val,-1,sizeof(val));}
int merge(int x,int y){
if(x==0||y==0) return x+y;
if(val[x]>val[y]||(val[x]==val[y]&&x>y)) swap(x,y);
ch[x][1]=merge(ch[x][1],y);
fa[ch[x][1]]=x;
if(dis[ch[x][0]]<dis[ch[x][1]]) swap(ch[x][0],ch[x][1]);
dis[x]=dis[ch[x][1]]+1;
return x;
}
inline int top(int a){return val[getf(a)];}
inline void pop(int a){
int x=getf(a);
val[x]=-1;
fa[ch[x][0]]=fa[ch[x][1]]=0;
merge(ch[x][0],ch[x][1]);
}
inline int getf(int x){
while(fa[x]) x=fa[x];
return x;
}
}t;
int main(){
int n=gi();RG int m=gi();
t.dis[0]=-1;
for(RG int i=1;i<=n;i++) t.val[i]=gi();
while(m--){
int opt=gi();
if(opt==1){
int x=gi(),y=gi();
if(t.val[x]==-1||t.val[y]==-1) continue;
if(x==y) continue;
int a=t.getf(x),b=t.getf(y);
t.merge(a,b);
}
if(opt==2){
int x=gi();
if(t.val[x]==-1) puts("-1");
printf("%d\n",t.top(x));
t.pop(x);
}
}
return 0;
}
Tips:评测地址