无旋Treap
解析
F
H
Q
T
r
e
a
p
FHQ Treap
FHQTreap和普通的
T
r
e
a
p
Treap
Treap都是一个二叉搜索堆,其同时满足二叉树的性质(左子树的权值小于等于当前节点权值,右子树权值大于当前节点权值)和堆的性质(对于小根堆,当前节点的优先级是堆中最小的)。
优点:
1,代码实现简单
2,可以实现可持久化
核心操作(以下均为小根堆)
初始化
我常用数组写法
int val[M],pos[M],siz[M],son[M][2];
//son[M][2] 记录左右儿子
//siz[M] 记录节点个数
//pos[M] 给每个节点随机的值,保证均摊下来复杂度为O(nlogn),它的值满足堆的性质
//val[M] 每个节点本身的价值,它的值满足二叉树的性质
split
s p l i t split split要实现的是将一颗 T r e a p Treap Treap分为两颗 T r e a p Treap Treap,其中左 T r e a p Treap Treap(以 a a a为根节点)中节点权值均小于等于(根据情况,也可以是小于)目标值 v v v,右 T r e a p Treap Treap(以 b b b为根节点)中节点权值均大于目标值 v v v。
给定根 r t rt rt,如果 v a l [ r t ] ≤ v val[rt]≤v val[rt]≤v,那么说明 r t rt rt及其左子树均可以划分给左树 a a a的左子树,接下来只需要考虑左树 a a a的右子树是什么。这时问题可以变为:对于给定的根 s o n [ r t ] [ 1 ] son[rt][1] son[rt][1],因为 r t rt rt的左子树已经划分给了 a a a),按照目标权值 v v v将其划分为以 s o n [ a ] [ 1 ] son[a][1] son[a][1]和右树 b b b的两颗 T r e a p Treap Treap。问题可以递归求解。
考虑另一种情况,如果 v a l [ r t ] > v val[rt]>v val[rt]>v,那么说明 r t rt rt及其右子树均可以划分给右树 b b b的右子树,按照刚才的套路,只需要考虑右树b的左子树是什么。问题进而变为:对于给定的根 s o n [ r t ] [ 0 ] son[rt][0] son[rt][0](因为 r t rt rt的右子树已经划分给了 b b b),按照目标权值 v v v将其划分为以 s o n [ b ] [ 0 ] son[b][0] son[b][0]和左树 a a a的两颗 T r e a p Treap Treap。问题可以递归求解。
void split(int rt,int &a,int &b,int v){//将根节点为rt的树,按v值分成分别以a,b为根的树
if(!rt){a=b=0;return;} //分到底了
if(val[rt]<=v) a=rt,split(son[rt][1],son[rt][1],b,v);
else b=rt,split(son[rt][0],a,son[rt][0],v);
pushup(rt);//更新节点个数
}
merge
m e r g e merge merge要实现的是,将两颗 T r e a p Treap Treap合并为一颗 T r e a p Treap Treap。合并有一个前提,要保证左树 a a a上任意节点的 v a l val val值都要小于右树 b b b上任意节点 v a l val val值。这样才能保证合并有序。这里维护的是小根堆。
如果当前 p o s [ a ] < p o s [ b ] pos[a]<pos[b] pos[a]<pos[b] ,为了同时满足小根堆和二叉搜索树的性质,那么应该将 b b b合并到 a a a的右子树上。
反之,应该将 a a a合并到 b b b的左子树上。递归求解即可,
int merge(int a,int b){//将根分别为a,b的两棵树合并,返回合并后树的根
int rt;
if(!a||!b) return a+b;//走到底了
if(pos[a]<pos[b]) rt=a,son[a][1]=merge(son[a][1],b);
else rt=b,son[b][0]=merge(a,son[b][0]);
pushup(rt);return rt;
}
模板题 P3369
代码
#include<bits/stdc++.h>
#define M 10000009
using namespace std;
const int mod=998244353;
int read(){
int f=1,re=0;
char ch;
for(ch=getchar();!isdigit(ch)&&ch!='-';ch=getchar());
if(ch=='-'){f=-1,ch=getchar();}
for(;isdigit(ch);ch=getchar()) re=(re<<3)+(re<<1)+ch-'0';
return re*f;
}
int val[M],pos[M],siz[M],cnt,root,n,son[M][2];
void pushup(int k){siz[k]=siz[son[k][0]]+siz[son[k][1]]+1;}
int build(int x){
val[++cnt]=x,siz[cnt]=1,pos[cnt]=rand()*rand()%mod;
return cnt;
}
void split(int rt,int &a,int &b,int v){
if(!rt){a=b=0;return;}
if(val[rt]<=v) a=rt,split(son[rt][1],son[rt][1],b,v);
else b=rt,split(son[rt][0],a,son[rt][0],v);
pushup(rt);
}
int merge(int a,int b){
int rt;
if(!a||!b) return a+b;
if(pos[a]<pos[b]) rt=a,son[a][1]=merge(son[a][1],b);
else rt=b,son[b][0]=merge(a,son[b][0]);
pushup(rt);return rt;
}
int kth(int rt,int v){
if(siz[son[rt][0]]>=v) return kth(son[rt][0],v);
if(siz[son[rt][0]]+1==v) return rt;
return kth(son[rt][1],v-siz[son[rt][0]]-1);
}
int main(){
srand(time(0));
n=read();
for(int i=1;i<=n;i++){
int opt=read(),x=read(),y=0,z=0,w=0;
if(opt==1) split(root,y,z,x),root=merge(merge(y,build(x)),z);
if(opt==2) split(root,y,z,x),split(y,y,w,x-1),w=merge(son[w][0],son[w][1]),root=merge(merge(y,w),z);
if(opt==3) split(root,y,z,x-1),printf("%d\n",siz[y]+1),root=merge(y,z);
if(opt==4) printf("%d\n",val[kth(root,x)]);
if(opt==5) split(root,y,z,x-1),printf("%d\n",val[kth(y,siz[y])]),root=merge(y,z);
if(opt==6) split(root,y,z,x),printf("%d\n",val[kth(z,1)]),root=merge(y,z);
}return 0;
}

本文深入解析无旋Treap数据结构,一种高效的二叉搜索堆,详细介绍了其核心操作如split和merge的实现原理及代码示例,适用于快速查询和更新场景。
2218

被折叠的 条评论
为什么被折叠?



