Treap
前言
为什么对于模拟赛中遇到的新知识,只要不写总结很快就忘了。
上个月刚做的 \(\text{Treap}\) 维护动态 \(\text{DP}\),现在就不会写 \(\text{Treap}\) 了。
二叉搜索树
二叉搜索树是一种满足如下性质的二叉树:
-
左子树所有点的权值比当前点小。
-
右子树所有点的权值比当前点大。
根据该性质,我们可以在 \(O(\text{depth})\) 的复杂度内完成许多操作。
简介
\(\text{Treap}\) 是一种弱平衡的二叉搜索树。
\(\text{Treap}\) 上的每个节点除了维护满足二叉搜索树性质的权值外,还额外维护了一个随机的优先级,其中优先级满足堆性质。
我们知道,单纯的二叉搜索树在某些数据下会退化成链,导致效率大幅降低。
而 \(\text{Treap}\) 通过维护随机的优先级满足堆性质,在一定程度上打乱了节点,从而缩减了二叉搜索树的期望高度。
本文主要记录无旋 \(\text{Treap}\)。
维护集合
按值分裂
传入一棵 \(\text{Treap}\) 的根以及阈值 \(lim\)。
将这棵 \(\text{Treap}\) 中的元素权值按与 \(lim\) 的大小关系分为两部分,得到两棵新树,并返回这两棵树的根。
void split(int rt,int lim,int &x,int &y){
if(!rt){
x=y=0;
return ;
}
if(ts[rt].val<=lim) x=rt,split(rs[rt],lim,rs[rt],y); // 根和左子树一定属于 <=lim 的部分
else y=rt,split(ls[rt],lim,x,ls[rt]); // 同理
pushup(rt);
return ;
}
按排名分裂
传入一棵 \(\text{Treap}\) 的根以及阈值 \(k\)。
将这棵 \(\text{Treap}\) 中的元素排名按与 \(k\) 的大小关系分为三部分,得到三棵新树,并返回这三棵树的根,其中有一棵树是单点。
这里的排名定义为严格小于自身的数的个数再 \(+1\)。
void split(int rt,int k,int &x,int &y,int &z){
if(!rt){ // 空树:三段都为空
x=y=z=0;
return ;
}
int lsiz=ts[ls[rt]].siz; // 左子树大小,依赖约定 ts[0].siz=0
if(k<=lsiz){ // 第 k 名在左子树中
z=rt; // 当前节点和整棵右子树的值都大于第 k 名,归入 z
split(ls[rt],k,x,y,ls[rt]); // 分裂左子树:x/y 由递归写入,第三段(大于 k 的部分)直接写回 ls[rt]
}else if(k<=lsiz+ts[rt].cnt){ // 第 k 名落在当前节点的重复值里
x=ls[rt],y=rt,z=rs[rt]; // 左子树全部更小给 x,当前节点自己是 y,右子树全部更大给 z
ls[rt]=rs[rt]=0; // y 要作为独立 treap 返回,先断开左右儿子
}else{ // 第 k 名在右子树中
x=rt; // 当前节点和整棵左子树的值都小于第 k 名,归入 x
split(rs[rt],k-lsiz-ts[rt].cnt,rs[rt],y,z); // 分裂右子树:第一段(小于 k 的部分)直接写回 rs[rt],y/z 由递归写入
}
pushup(rt);
return ;
}
合并
传入两棵 \(\text{Treap}\) 的根 \(x\) 和 \(y\),将其合并为一棵树。
这里需要保证 \(x\) 内所有点的权值均小于 \(y\) 内所有点的权值。
int merge(int x,int y){
if(!x||!y) return x+y; // 有一棵为空,直接返回另一棵
if(ts[x].pr<ts[y].pr){ // x 优先级更高(小根堆),x 当根
rs[x]=merge(rs[x],y); // y 与 x 的右子树合并,接到 x 右边(rs[x] 的 key 全小于 y 的 key)
pushup(x);
return x;
}else{ // y 优先级更高,y 当根
ls[y]=merge(x,ls[y]); // x 与 y 的左子树合并,接到 y 左边(x 的 key 全小于 ls[y] 的 key)
pushup(y);
return y;
}
}

浙公网安备 33010602011771号