左偏树学习笔记
左偏树
这绝对是世界上最可爱的数据结构吧!
首先左偏树是一颗二叉树。
左偏树有两个关键词:键值,距离(dist)
前者在树上满足堆的性质,后者是维护树平衡的关键!
距离的定义:
一个结点称为外结点,当且仅当该节点的左子树或者右子树为空。
一个结点的距离定义为它到子树中最近的外结点的距离,如果他本身是外结点,则为 \(0\)
一棵左偏树的距离是根节点的距离。
空结点的距离定义为 \(-1\)。
左偏树的性质:
-
键值满足堆的性质,即当前节点的键值小于等于当前节点的孩子的键值(这里以维护最小值为例,最大值则大于等于)。
-
节点的左子节点的距离不小于节点的右子节点的距离。这也是为什么他左偏,如果反过来就是右偏了。
-
左偏树节点的左右儿子都是左偏树
-
节点的距离等于节点的右儿子距离 $ + 1$,因为是最短,而右儿子又不大于左儿子的距离 。
严格定义:
左偏树是具有左偏性质的堆有序二叉树。
以上都很好理解,接下来也不难。
贺几个引理和推论吧
- 对于一定的左偏树距离,节点数最小的必定是完全二叉树
- 证明:最小的情况必定满足对于任意节点,左右儿子距离相等,而这正好是完全二叉树。
- 若一颗左偏树的距离为 \(k\) ,那么节点数至少为 \(2^{k+1} - 1\) ,由上面第一条可知,最少情况是完全二叉树。
- 一个 \(n\) 个节点的左偏树的距离最大为 \(\left \lfloor \log_2(n+1) \right \rfloor - 1\)
- 证明:\(n \ge 2^{k+1} - 1\) 所以 \(k \le \left \lfloor \log_2(n+1) \right \rfloor - 1\)
左偏树的合并
我个人感觉就是,和线段树合并好像啊!
-
首先对于当前合并的 A,B两棵树,假设 \(val[root-A] \le val[root-B]\), 那么将 B 合并到 A 的右儿子就好了,这一步递归处理。
-
维护树的左偏性质,如果 \(dist[right] > dist[left]\) 交换左右儿子。
-
更新根节点距离,也就是右儿子距离 \(+1\) 。
左偏树删除根节点(堆顶)
合并左右儿子,没了qwq。
左偏树整体加、减、乘(正数)
和线段树平衡树等等一样,打一个lazy_tag就好了qwq,别忘了pushdown。
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define Rep(i,a,b) for(int i=(a);i<(b);++i)
#define rrep(i,a,b) for(int i=(a);i>=(b);--i)
using namespace std;
template <typename T>
inline void read(T &x){
x=0;char ch=getchar();bool f=0;
while(ch<'0'||ch>'9'){if(ch=='-')f=1;ch=getchar();}
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
if(f)x=-x;
}
template <typename T,typename ...Args>
inline void read(T &tmp,Args &...tmps){read(tmp);read(tmps...);}
const int N = 1e5 + 5;
int n,m,fa[N],tag[N],ch[N][2],dist[N];
struct qwq{
int id,val;
bool operator <(const qwq& x)const{return val != x.val ? val < x.val : id < x.id;}
}tr[N];
int findfa(int x){return fa[x] == x ? x : fa[x] = findfa(fa[x]);}
int merge(int x,int y){
if(!x || !y)return x | y;
if(tr[y] < tr[x])swap(x,y);
ch[x][1] = merge(ch[x][1],y);
if(dist[ch[x][0]] < dist[ch[x][1]])swap(ch[x][0],ch[x][1]);
dist[x] = dist[ch[x][0]] + 1;
return x;
}
signed main(){
dist[0] = -1;
read(n,m);
rep(i,1,n)read(tr[i].val),fa[i] = i,tr[i].id = i;
while(m--){
int op,x,y;
read(op,x);
if(op == 1){
read(y);
if(tag[x] || tag[y])continue;
x = findfa(x),y = findfa(y);
if(x == y)continue;
fa[x] = fa[y] = merge(x,y);
}
else{
if(tag[x]){
puts("-1");
continue;
}
x = findfa(x);tag[x] = 1;
printf("%d\n",tr[x].val);
fa[ch[x][0]] = fa[ch[x][1]] = fa[x] = merge(ch[x][0],ch[x][1]);
ch[x][0] = ch[x][1] = dist[x] = 0;
}
}
}