左偏树
Noip大概率翻皮水了,然后先继续xjb学习吧,顺便文化课也是翻皮水大队的:(
今天介绍一种特殊的数据结构:可并堆中的一种->左偏树(好吧其实是因为这种简单易懂代码复杂度较低).
基本介绍
左偏树,故名思义,它是颗向左倾斜的树,其实,它还是棵二叉树,再者,它还具有堆的性质,but,它不是堆.
那么显然,左偏树看起来就像是优化堆一些难以用较优复杂度实现的操作,其实主要就是一个操作:合并.
我们知道,传统的二叉堆,是需要暴力合并的,复杂度为(O(sz1+sz2)),而本文所涉及的左偏树,复杂度为$O(log_{2} sz1sz2) $.
首先介绍一个定义:一个节点到其子树内最近叶节点的距离称之为这个节点的高度Height,简记为ht.
接下来给出一些左偏树的性质.
基本性质
性质1
堆的性质:对于任意节点P,(val_{P})<(或>)(val_{lson[p]})与(val_{rson[p]})
性质2
左偏树顾名思义,向左倾斜的树,就是这个性质在图像可视化后的诠释.
性质3
很好理解,由性质2以及叶节点ht为0可以得出.
性质4
对于一棵(n)节点的左偏树,(max{ht} leq log_{2}(n+1)-1)
给出证明如下,设(max{ht}=k)
那么,显然节点数最少的情况为一棵满二叉树,此时,节点数为(2^{k+1}-1)
故(n geq 2^{k+1}-1),由数学推导可得性质4.
这个性质主要保证了左偏树的复杂度.
接下来简单介绍一下一些基本操作.
基本操作
1.合并
这是左偏树最基础也是最重要的操作,这里介绍小根堆的情况:
首先,令根节点权值较小的为x,否则为y,记根节点分别为X、Y.
首先在根节点的右子树最右链中找到第一个比Y大的位置,将Y作为其父亲,然后不断递归调用合并Y的右子树和以该节点为根的右子树即可,并维护更新相关信息.
然后发现更新后,右子树的ht可能比左子树大,此时交换两个子树即可.
其实上述合并过程的实现本质是一样的,下面给出参考,对于复杂度的分析,不多给出解释.
inline int merge(int x,int y){
if (!x||!y) return x+y;
if (v[x]>v[y]||(v[x]==v[y]&&x>y)) swap(x,y);
ch[x][1]=merge(ch[x][1],y);
f[ch[x][1]]=x;
if (ht[ch[x][0]]<ht[ch[x][1]]) swap(ch[x][0],ch[x][1]);
ht[x]=ht[ch[x][1]]+1;return x;
}
2.插入
本质上就是将一棵只有一个节点的左偏树与当前树合并.
3.查询最大/最小值
直接返回根结点的权值.
4.删除根节点
合并根节点两棵子树即可.
5.构造一棵新左偏树
法1:
暴力插入
法2:
分治思想,分段处理,直至剩余1个节点,然后返回并merge,复杂度约为O(n).
6.删除任意节点
子树内维护与删除根节点一样,然后向上传递信息并更改,维护性质即可.
模板
给出模板题的模板
见下:
#include <stdio.h>
#define MN 100005
#define R register
inline int read(){
R int x; R bool f; R char c;
for (f=0; (c=getchar())<'0'||c>'9'; f=c=='-');
for (x=c-'0'; (c=getchar())>='0'&&c<='9'; x=(x<<3)+(x<<1)+c-'0');
return f?-x:x;
}
inline int max(int a,int b){return a>b?a:b;}
inline int min(int a,int b){return a<b?a:b;}
inline void swap(int &a,int &b){a^=b,b^=a,a^=b;}
int n,q,ch[MN][2],v[MN],ht[MN],f[MN];
inline int gf(int u){
while(f[u]) u=f[u];
return u;
}
inline int merge(int x,int y){
if (!x||!y) return x+y;
if (v[x]>v[y]||(v[x]==v[y]&&x>y)) swap(x,y);
ch[x][1]=merge(ch[x][1],y);
f[ch[x][1]]=x;
if (ht[ch[x][0]]<ht[ch[x][1]]) swap(ch[x][0],ch[x][1]);
ht[x]=ht[ch[x][1]]+1;return x;
}
inline void pop(int x){
v[x]=-1;f[ch[x][0]]=f[ch[x][1]]=0;
merge(ch[x][0],ch[x][1]);
}
int main(){
n=read(),q=read();ht[0]=-1;
for (R int i=1; i<=n; ++i) v[i]=read();
for (R int i=1; i<=q; ++i)
if (read()==1){
R int x=read(),y=read();
if (!(~v[x])||!(~v[y])) continue;
x=gf(x),y=gf(y);if (x==y) continue;
merge(x,y);
}
else{
R int x=read();
if (!(~v[x])) puts("-1");
else {x=gf(x);printf("%d
",v[x]);pop(x);}
}
return 0;
}