什么是左偏树?
左偏树( Leftisttree )是可并堆( Mergeableheap )的一种
相比于 priorityqueue ,它还支持 Merge (合并两个堆) 操作
左偏树上的每个节点不仅保存了 val ,还存储了 lc (左儿子), rc (右儿子)和 dis 。
这里的 dis 是什么呢?
首先定义一个“外节点”的概念
如果一个节点 x ,它的左子树或者右子树为空,即还能在
x 这个节点合并上另一个堆,就称 x 为外节点
对于一个节点
定义外节点的 dis=0
- 举个栗子:
图中的数字代表每个节点的 dis 值
左偏树的性质
这里我用 struct
来存一棵左偏树
struct Leftist_Tree {
int val,lc,rc,dis;
bool operator < (const Leftist_Tree &q)const {
return val<q.val;
}
};
Leftist_Tree t[MAXN];
这里以小根堆为例
性质 1
设 fa[x] 表示 x 节点的父亲,那么显然有
t[fa[x]].val<t[x].val (堆的性质)
性质 2
节点的左儿子的距离不小于右儿子的距离
即 t[lc].dis≥t[rc].dis ,这个性质被称作左偏性质
左偏树,字面上的意思就是向左偏的树,也就是说这棵树左边的节点数一定比较多
那这样的话,每次从右子树找外节点一定比从左子树找外节点快
根据这个还可以得出一个推论:
一个节点的左子树和右子树都是左偏树
性质 3
对于一个有右儿子的节点 x ,有
t[x].dis=t[t[x].rc].dis+1
也就是说, 一个节点的 dis 等于它右儿子的 dis+1
为了让这个性质对没有右儿子的节点也满足,我们定义空节点的 dis=−1
这样性质3就可以表示为
t[0].dis=−1∀x∈N+,t[x].dis=t[t[x].rc].dis+1
实在不懂的话,看上面的图的 dis 值就好了
左偏树的一些骚操作
Merge
Merge 可以说是左偏树的核心操作了,其余的操作都是基于 Merge 完成的
上图(图中的数字表示每个节点的 val )
我们要现在合并这两个左偏树,第一步就是找到一个外节点,然后把它并上去
在 性质2 当中我们提到
每次从右子树找外节点一定比从左子树找外节点快
所以我们每次都贪心的找右子树
从根节点开始,
7
是第一个从右子树找到的外节点,而且
这时候重复上面的过程,第一个从右子树找到的外节点是
8
,而且也符合堆的性质,把
这个时候好像是合并完成了,但是其实没有,因为这个时候你会发现这不是左偏树了
我们合并出了一个假的左偏树,GG
我们来想办法让这棵树重新左偏,我们先从
这时我们为了让树左偏,直接 swap(lc,rc) 就可以了。
这是很显然也是很简单的方法。
swap 之后树变成了这样
再回溯到
7
,我们开心地发现
最后回溯到
5
,依旧不满足性质2,不怕,
到此为止, Merge 结束了
我们可以发现这其实是一个一直递归回溯判断的过程
然后判断是不是左偏的话,我们利用了性质2
所以
Merge
的代码就可以很自然地写出来了
#define Lc t[x].lc
#define Rc t[x].rc
int fa[MAXN];
int Merge(int x,int y) {
if(!x||!y) return x+y;
//如果有一个节点是空节点,那么merge之后的根就是x+y
if(t[y]<t[x]) swap(x,y);
if(t[x].val==t[y].val&&x>y) swap(x,y);
//我们在上面的图中,并没有体现出上面的swap
//因为是小根堆,所以肯定是大的并到小的上面去,所以有if(t[y]<t[x]) swap(x,y);
//可能写成t[x]>t[y] 好理解一点,但是我只重载了 < ,所以就这么写了
//第二个if就是让编号大的并到编号小的上去
Rc=Merge(Rc,y);
//y一直和x的右子树合并
fa[Rc]=x;
//合并了,也不能忘了自己的爸爸是谁
if(t[Lc].dis<t[Rc].dis) //利用性质2判断是否左偏
swap(Lc,Rc);
t[x].dis=t[Rc].dis+1;//利用性质3来更新dis
return x;//返回合并后的根,方便回溯来维护左偏
}
左偏树经常和并查集结合到一起,因为你要判断合并的点是不是在一棵左偏树里,但是这个并查集不要带路径压缩
也就是说 Find 要这么写
int Find(int x) {
for(;fa[x];x=fa[x]);
return x;
}
到这里, Merge 讲完了
pop
pop 操作异常的简单,直接 Merge(Lc,Rc) 就可以了
比较好理解,没什么好说的,注意打一个不在树中的标记和把他清零就好了
贴一下代码
bool not_intree[MAXN];
void pop(int x) {
not_intree[x]=true;
fa[Lc]=fa[Rc]=0;
Merge(Lc,Rc);
return;
}
insert
insert 相当于把一个只有一个节点的左偏树和整棵左偏树 Merge 就可以了
可以用左偏树来做的题目
简单题
直接贴代码了
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
template<typename T>
void input(T &x) {
x=0; T a=1;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar())
if(c=='-') a=-1;
for(;c>='0'&&c<='9';c=getchar())
x=x*10+c-'0';
x*=a;
return;
}
#define MAXN 100010
struct Leftist_Tree {
int lc,rc,val,dis;
bool operator < (const Leftist_Tree &q)const {
return val<q.val;
}
bool operator == (const Leftist_Tree &q)const {
return val==q.val;
}
};
Leftist_Tree t[MAXN];
int fa[MAXN];
#define Lc t[x].lc
#define Rc t[x].rc
int Merge(int x,int y) {
if(!x||!y) return x+y;
if(t[y]<t[x]) swap(x,y);
if(t[x]==t[y]&&x>y) swap(x,y);
fa[Rc=Merge(Rc,y)]=x;
if(t[Lc].dis<t[Rc].dis)
swap(Lc,Rc);
t[x].dis=t[Rc].dis+1;
return x;
}
bool not_intree[MAXN];
void pop(int x) {
not_intree[x]=true;
fa[Lc]=fa[Rc]=0;
Merge(Lc,Rc);
return;
}
#undef Lc
#undef Rc
int Find(int x) {
for(;fa[x];x=fa[x]);
return x;
}
int main() {
int n,m;
input(n),input(m);
for(int i=1;i<=n;i++)
input(t[i].val);
t[0].dis=1;
for(int op,x,y;m;m--) {
input(op);
if(op==1) {
input(x),input(y);
if(x==y) continue;
if(not_intree[x]||not_intree[y]) continue;
Merge(Find(x),Find(y));
} else {
input(x);
if(not_intree[x]) puts("-1");
else {
printf("%d\n",t[y=Find(x)].val),
pop(y);
}
}
}
return 0;
}
这道题需要大根堆,在我的代码里,只需要改重载运算符就可以了
建议大家也这么写
这道题在 pop 的时候需要返回 pop 之后的根,而且不需要打 notintree 标记
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
template<typename T>
void input(T &x) {
x=0; T a=1;
register char c=getchar();
for(;c<48||c>57;c=getchar())
if(c==45) a=-1;
for(;c>=48&&c<=57;c=getchar())
x=x*10+c-48;
x*=a;
return;
}
#define MAXN 100010
struct Leftist_Tree {
int lc,rc,val,dis;
bool operator < (const Leftist_Tree &q)const {
return val>q.val;
}
bool operator == (const Leftist_Tree &q)const {
return val==q.val;
}
};
Leftist_Tree t[MAXN];
int fa[MAXN];
int Find(int x) {
for(;fa[x];x=fa[x]);
return x;
}
#define Lc t[x].lc
#define Rc t[x].rc
int Merge(int x,int y) {
if(!x||!y) return x+y;
if(t[y]<t[x]) swap(x,y);
if(t[x]==t[y]&&x>y) swap(x,y);
fa[Rc=Merge(Rc,y)]=x;
if(t[Lc].dis<t[Rc].dis)
swap(Lc,Rc);
t[x].dis=t[Rc].dis+1;
return x;
}
int pop(int x) {
fa[Lc]=fa[Rc]=0;
int ans=Merge(Lc,Rc);
Lc=0,Rc=0;
return ans;
}
#undef Lc
#undef Rc
int n;
void Clear() {
for(int i=0;i<=n;i++) {
fa[i]=0;
t[i].lc=t[i].rc=t[i].dis=0;
}
t[0].dis=1;
return;
}
int main() {
while(~scanf("%d",&n)) {
Clear();
for(int i=1;i<=n;i++)
input(t[i].val);
int m;
input(m);
for(int x,y;m;m--) {
input(x),input(y);
x=Find(x),y=Find(y);
if(x==y) puts("-1");
else {
t[x].val>>=1,t[y].val>>=1;
int rt1=Merge(x,y),
rt2=Merge(pop(x),pop(y));
printf("%d\n",t[Merge(rt1,rt2)].val);
}
}
}
return 0;
}
好像 HDU 也有这道题
难题
写不来的。。