算法大意
左偏树中,每个节点被赋予了两个值,一个是他本身的value,另一个是他的深度(?可能不太恰当)dist,同过dist可以方便的实现两个堆的合并。
左偏树中,要求任何一个节点,其 leftson 的dist 值要大于其 rightson 的dist值,也就是说,左子树将会比右子树深(当然,这并不是堆的排序标准,堆顶元素究竟是什么要依据题目来定义大根堆还是小根堆,和优先队列是一样的,这里不再多说)
上面的定义请多读几遍,这段话虽然很短,但真正理解起来是有困难的。1. 一个左偏树把跟去掉以后,它的左儿子和右儿子各自也是一颗左偏树(精华所在)2. 整颗树虽然有可能会被卡成一条链,但其复杂度均摊下来每个操作都是log级别的。3. 删除和加入节点的方式不能用普通堆的手段了,因为那样会破坏dist数组,使得左偏性质遭到毁灭性打击。
可支持的操作
合并!这是最重要也是最基础的操作。
查询最大值(最小值)
删除最大值(最小值)(依赖于合并操作!)
插入节点(依赖于合并操作!)
(本篇博客只用到这么多)
合并: 将两个左偏树合并为一颗
这里使用 rs[ ],ls[ ],dist[ ],val[ ]分别表示右儿子,左儿子,深度,值。
样例为大根堆的合并操作:采用递归合并的思想(还记得前面的内容么,删掉任意节点后剩下的两个儿子仍为左偏树)
int merge(int x,int y){
if(!x||!y)return x^y;//如果有一个子树为空,则返回另一个,另一种写法为return x|y;
if(val[x] < val[y])swap(x,y);//保留较大的
rs[x] = merge(rs[x] ,y);//保持左偏,右子树递归合并
if(dist[rs[x]] > dist[ls[x]])swap(t[x].ls, t[x].rs);//保证左子树dist>右子树dist
dist[x] = dist[rs[x]]+1;//根节点dist=右儿子dist + 1;
return x;//合并后的根节点
}
代码其实相当短,但要想理解并熟练应用还要下工夫
有了这个合并操作,我们就可以做很多事情了;
想要插入一个值怎么办?
把这个值所对应的dist设为0,这样它就抽象为了只有一个节点的左偏树,然后直接merge一下即可。
想要删除根节点怎么办?
咱们不是有合并么,把根节点的左右儿子合并根节点不就没了么QAQ。
//吐槽一句,merge函数累不累啊。。。
顺便附上Luogu左偏树模板题的 AC 代码:
第一次的代码(写的比较丑)(啊哈哈哈左偏树我是一次AC的)
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int fa[N];
int v[N],dist[N],ls[N],rs[N];
bool del[N];
int n,m;
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
int merge(int x,int y){
if(!x||!y)return x|y;
if(v[x]>v[y])swap(x,y);
rs[x] = merge(rs[x] ,y);
if(dist[rs[x]] > dist[ls[x]])swap(ls[x],rs[x]);
dist[x] = dist[rs[x]]+1;
return x;
}
void insert(int x,int y){
x = find(x),y = find(y);
if(x != y)fa[x] = fa[y] = merge(x , y);
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&v[i]);
dist[i] = 0;
fa[i] = i;
}
int op,x,y;
while(m--){
scanf("%d",&op);
if(op==1){
scanf("%d%d",&x,&y);
if(del[x]||del[y])continue;
insert(x,y);
} else {
scanf("%d",&x);
if(del[x]){
puts("-1");
} else {
x = find(x);
cout<< v[x] <<endl;
int ro = merge(rs[x],ls[x]);
fa[x] = fa[ro] = ro;
del[x] = true;
}
}
}
}
用结构体来存储的代码(除了合并部分代码略有啰嗦其他还好)
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int read(){
int q=0;
char c=getchar();
while(c>'9'||c<'0')c=getchar();
while(c>='0'&&c<='9')q=q*10+c-'0',c=getchar();
return q ;
}
int n,m;
struct node{
int val,dist,ls,rs;
}t[N];
bool del[N];
int fa[N];
int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
int merge(int x,int y){
if(!x||!y)return x^y;
if(t[x].val > t[y].val)swap(x,y);
t[x].rs = merge(t[x].rs ,y);
if(t[t[x].rs].dist > t[t[x].ls].dist)swap(t[x].ls, t[x].rs);
t[x].dist = t[t[x].rs].dist+1;
return x;
}
int main(){
n = read(); m = read();
for(int i = 1;i <= n; i++){
t[i].val = read();
t[i].dist = 0;
fa[i] = i;
}
int x,y,op;
while(m--){
op = read();
if(op == 1){
x = read(); y = read();
if(del[x] || del[y])continue;
x = find(x); y = find(y);
if(x == y)continue;
int ro = merge(x , y);
fa[x] = fa[y] = ro;
} else {
x = read();
if(del[x]){
puts("-1");
} else {
x = find (x) ;
printf("%d\n",t[x].val);
int ro = merge(t[x].rs,t[x].ls);
fa[x] = fa[ro] = ro;
del[x] = true;
}
}
}
}