treap学习笔记
推荐一篇洛谷日报 讲的超级清晰/bx
1.treap
首先 treap=tree+heap
即 二叉搜索树+堆
二叉搜索树:指根的左儿子比根小,右儿子比根大的二叉树
显然它有一个性质:它的中序遍历一定是一个有序序列
但是还有一个问题:树可能退化成一条链(单次复杂度\(O(\log n)\)-->\(O(n)\))
那么我们就可以给每个点取随机数 再用堆的方式维护
这样做就能让深度更平均 防止复杂度假掉
2.操作
1.维护信息:
int siz[N],num[N],rd[N],v[N],son[N][2];
siz[k]:k点的子树大小
num[k]:k点的存的数字的数量
rd[k]:k点的随机数(用于堆排序)
v[k]:k点维护的值
son[k][0/1]:k点的左/右儿子
2.pushup
inl void pushup(int k){siz[k]=siz[son[k][0]]+siz[son[k][1]]+num[k];}
同线段树 合并子树信息
3.旋转
//d=0 左旋 d=1 右旋
inl void rotate(int &k,int d){
int p=son[k][d^1];
son[k][d^1]=son[p][d];
son[p][d]=k;
pushup(k);
pushup(p);
k=p;
}
大致操作就是 如果右旋 就把父节点A接到左儿子B下面 再把B的右儿子变成A的左儿子 左旋同理
贺张巨佬的图
并且观察一下我们发现 rotate操作前后树的中序遍历相同
也就是我们可以用它在不影响其搜索树性质情况下 维护其堆的性质
4.插入
void ins(int &k,int x){
if(!k){
k=++cnt;num[k]=siz[k]=1;
v[k]=x;rd[k]=rand();
return;
}
if(x==v[k]){num[k]++;siz[k]++;return;}
int d=(x<v[k]);
ins(son[k][d^1],x);
if(rd[k]<rd[son[k][d^1]])
rotate(k,d);
pushup(k);
}
1.当前为空结点:直接添加信息并返回
2.当前节点值与x一致:num、siz++
3.否则根据大小向左右子树递归 最后记得旋转+pushup
5.删除
void del(int &k,int x){
if(!k)return;
if(x<v[k])del(son[k][0],x);
else if(x>v[k])del(son[k][1],x);
else{
if(num[k]>1){
num[k]--;siz[k]--;return;
}else if(!son[k][0]&&!son[k][1]){
num[k]--;siz[k]--;k=0;
}else if(son[k][0]&&!son[k][1]){
rotate(k,1);
del(son[k][1],x);
}else if(!son[k][0]&&son[k][1]){
rotate(k,0);
del(son[k][0],x);
}else if(son[k][0]&&son[k][1]){
int d=(rd[son[k][0]]<rd[son[k][1]]);
rotate(k,d^1);del(son[k][d^1],x);
}
}
pushup(k);
}
1.访问到空节点:没找到 直接返回
2.如果当前节点值与x不同 向左右子树递归
3.当前节点值与x相同且数量>1:直接删除一个并返回
4.否则节点值与x相同且数量=1:将该节点旋转到叶子结点再删除 具体细节看代码
6.查询排名
int rk(int k,int x){
if(!k)return 1;
if(x==v[k])return siz[son[k][0]]+1;
if(x<v[k])return rk(son[k][0],x);
return siz[son[k][0]]+num[k]+rk(son[k][1],x);
}
1.查询到空结点:只会在x不在树中时出现 此时排名要加1(调了一个小时/fn)
2.当前节点值等于x:显然排名为左子树大小+1
3.x比当前点值小:递归左树
4.否则x比当前点值大:左子树大小+当前点数量+右子树排名(应该很好理解吧)
7.查询第x大的值
int find(int k,int x){
if(!k)return 0;
if(x<=siz[son[k][0]])
return find(son[k][0],x);
if(x>siz[son[k][0]]+num[k])
return find(son[k][1],x-siz[son[k][0]]-num[k]);
return v[k];
}
这个操作比较类似于主席树
1.查询到空结点:没找到 返回
2.x小于左子树大小:递归左树
3.x比左子树+当前点大小还大:递归右树 找第x-siz[左子树]-num[当前点]个
4.否则第x个就是当前节点 返回当前节点值
8.前驱/后继
这俩差不多 放一起了
先看前驱(前驱定义为小于x,且最大的数):
int pre(int k,int x){
if(!k)return -inf;
if(v[k]>=x)return pre(son[k][0],x);
return max(v[k],pre(son[k][1],x));
}
1.找到空节点:没有前驱 返回负无穷
2.当前点等于x或比x大:显然不符合要求 左子树递归找更小的
3.否则当前点值小于x:符合要求 在当前点和右子树中找最小值
后继一样:
int suc(int k,int x){
if(!k)return inf;
if(v[k]<=x)return suc(son[k][1],x);
return min(v[k],suc(son[k][0],x));
}
最后是完整代码:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inl inline
#define gc getchar
#define pc putchar
const int N=5e5+5;
const int M=1e5+5;
const int inf=INT_MAX;
const int mod=9901;
inl int read(){
int x=0,f=1;char c=gc();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=gc();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=gc();}
return x*f;
}
inl void write(int x){
if(x<0){pc('-');x=-x;}
if(x>9)write(x/10);
pc(x%10+'0');
}
inl void writel(int x){write(x);pc('\n');}
int n,cnt,rt,op,x;
struct treap{
int siz[N],num[N],rd[N],v[N],son[N][2];
inl void pushup(int k){siz[k]=siz[son[k][0]]+siz[son[k][1]]+num[k];}
inl void rotate(int &k,int d){
int p=son[k][d^1];
son[k][d^1]=son[p][d];
son[p][d]=k;
pushup(k);
pushup(p);
k=p;
}
void ins(int &k,int x){
if(!k){
k=++cnt;num[k]=siz[k]=1;
v[k]=x;rd[k]=rand();
return;
}
if(x==v[k]){num[k]++;siz[k]++;return;}
int d=(x<v[k]);
ins(son[k][d^1],x);
if(rd[k]<rd[son[k][d^1]])
rotate(k,d);
pushup(k);
}
void del(int &k,int x){
if(!k)return;
if(x<v[k])del(son[k][0],x);
else if(x>v[k])del(son[k][1],x);
else{
if(num[k]>1){
num[k]--;siz[k]--;
return;
}else if(!son[k][0]&&!son[k][1]){
num[k]--;siz[k]--;k=0;
}else if(son[k][0]&&!son[k][1]){
rotate(k,1);
del(son[k][1],x);
}else if(!son[k][0]&&son[k][1]){
rotate(k,0);
del(son[k][0],x);
}else if(son[k][0]&&son[k][1]){
int d=(rd[son[k][0]]<rd[son[k][1]]);
rotate(k,d^1);del(son[k][d^1],x);
}
}
pushup(k);
}
int rk(int k,int x){
if(!k)return 1;
if(x==v[k])return siz[son[k][0]]+1;
if(x<v[k])return rk(son[k][0],x);
return siz[son[k][0]]+num[k]+rk(son[k][1],x);
}
int find(int k,int x){
if(!k)return 1;
if(x<=siz[son[k][0]])
return find(son[k][0],x);
if(x>siz[son[k][0]]+num[k])
return find(son[k][1],x-siz[son[k][0]]-num[k]);
return v[k];
}
int pre(int k,int x){
if(!k)return -inf;
if(v[k]>=x)return pre(son[k][0],x);
return max(v[k],pre(son[k][1],x));
}
int suc(int k,int x){
if(!k)return inf;
if(v[k]<=x)return suc(son[k][1],x);
return min(v[k],suc(son[k][0],x));
}
}tree;
signed main(){
n=read();
while(n--){
op=read();x=read();
if(op==1)tree.ins(rt,x);
if(op==2)tree.del(rt,x);
if(op==3)writel(tree.rk(rt,x));
if(op==4)writel(tree.find(rt,x));
if(op==5)writel(tree.pre(rt,x));
if(op==6)writel(tree.suc(rt,x));
}
return 0;
}