$\text{Treap}$ 的无旋版本,支持快速分裂、合并以及可持久化。
$\text{Treap}$ 保证深度的方法为给每个结点赋随机的优先级 $r$,权值方面 $\text{Treap}$ 为二叉搜索树,优先级方面 $\text{Treap}$ 为堆。
而当每个点的权值 $val$、$r$ 确定后树的形态唯一,这与操作顺序无关。而由于 $r$ 的随机性,保证了 $\text{Treap}$ 的深度。
$\text{fhq treap}$ 利用了 $\text{Treap}$ 的这条性质,实现了 $O(\log n)$ 的 $\text{split}$ 和 $\text{merge}$。
$\text{split}$ 操作分两种,分别用于维护集合和序列。第一种 $\text{split}$ 操作根据权值 $v$ 将 $\text{fhq treap}$ 分成两部分。
每层 $\text{split}$ 都会到达结点 $k$ 的子节点,所以时间复杂度为原树的深度,根据 $\text{Treap}$ 的性质,深度为 $O(\log n)$。
void split(int k,int &k1,int &k2,int v){ if(!k) return k1=k2=0,void(); if(node[k].val<=v){ k1=k;split(node[k].ch[1],node[k1].ch[1],k2,v);pushup(k1); }else{ k2=k;split(node[k].ch[0],k1,node[k2].ch[0],v);pushup(k2); } }
另一种 $\text{split}$ 操作根据节点数 $\text{sz}$ 将 $\text{fhq treap}$ 分成两部分。
void split(int k,int &k1,int &k2,int rk){ if(!k) return k1=k2=0,void(); pushdown(k); if(node[node[k].ch[0]].sz+1<=rk){ k1=k;split(node[k].ch[1],node[k1].ch[1],k2,rk-node[node[k].ch[0]].sz-1);pushup(k1); }else{ k2=k;split(node[k].ch[0],k1,node[k2].ch[0],rk);pushup(k2); } }
$\text{merge}$ 操作根据优先级 $r$ 合并两棵树,要求保证前一棵树的结点权值均小于后一棵树的结点权值。
每层 $\text{merge}$ 都会到达结点 $k1$ 或 $k2$ 的子节点,所以时间复杂度为两树的深度和,根据 $\text{Treap}$ 的性质,深度为 $O(\log n)$。
void merge(int &k,int k1,int k2){ if(!k1||!k2)return k=k1|k2,void(); if(node[k1].r>node[k2].r){ k=k1;merge(node[k].ch[1],node[k1].ch[1],k2);pushup(k); }else{ k=k2;merge(node[k].ch[0],k1,node[k2].ch[0]);pushup(k); } }
有了 $\text{split}$ 和 $\text{merge}$ 操作,不难得到其他操作。