Treap 前置知识:朴素二叉搜索树 ,堆基础 。
简介 Treap(树堆)是一种 弱平衡  的 二叉搜索树 。
Treap 的结点除了被维护的 权值 (𝑣 𝑎 𝑙 val 优先级 (𝑝 𝑟 𝑖 𝑜 𝑟 𝑖 𝑡 𝑦 priority 
其中,二叉搜索树的性质是指:
左子节点的权值(𝑣 𝑎 𝑙 val  右子节点的权值(𝑣 𝑎 𝑙 val  堆的性质是:
子节点优先级(𝑝 𝑟 𝑖 𝑜 𝑟 𝑖 𝑡 𝑦 priority  不难看出,如果用的是同一个值,那么这两种数据结构在组合后会变成一条链,所以我们再在搜索树的基础上,引入一个给堆的值 𝑝 𝑟 𝑖 𝑜 𝑟 𝑖 𝑡 𝑦 priority 𝑣 𝑎 𝑙 val 𝑝 𝑟 𝑖 𝑜 𝑟 𝑖 𝑡 𝑦 priority 𝑝 𝑟 𝑖 𝑜 𝑟 𝑖 𝑡 𝑦 priority 
下图就是一个 Treap 的例子(这里使用的是小根堆,即根节点的优先级最小)。
那我们为什么需要大费周章的去让这个数据结构符合树和堆的性质,并且随机给出堆的值呢?
要理解这个,首先需要理解朴素二叉搜索树的问题。在给朴素搜索树插入一个新节点时,我们需要从这个搜索树的根节点开始递归,如果新节点比当前节点小,那就向左递归,反之亦然。
最后当发现当前节点没有子节点时,就根据新节点的值的大小,让新节点成为当前节点的左或右子节点。
如果插入结点的权值是随机的(换言之,是随机插入的),那这个朴素搜索树的高度较小(接近 l o g  𝑛 log  n 𝑛 n 𝑂 ( l o g  𝑛 ) O ( log  n ) 
不过,这只是在随机情况下的复杂度,如果我们按照下面这个非常有序的顺序给一个朴素的搜索树插入节点:
那么这棵树将会退化成链,即变得非常「瘦长」(每次插入的节点都比前面的大,所以都被安排到右子节点了):
不难看出,查询的复杂度也从 𝑂 ( l o g  𝑛 ) O ( log  n ) 𝑂 ( 𝑛 ) O ( n ) 
而 treap 为了解决这个问题、达到一个较为「平衡」的状态,通过维护随机的优先级满足堆性质,「打乱」了节点的插入顺序,从而让二叉搜索树达到了理想的复杂度,避免了退化成链的问题。
Treap 复杂度的证明 由于 treap 各种操作的复杂度都和所操作的结点的深度有关,我们首先证明,所有结点的期望高度都是 𝑂 ( l o g  𝑛 ) O ( log  n ) 
记号约定 为了方便表述,我们约定:
𝑛 n Treap 结点中满足二叉搜索树性质的称为 权值 ,满足堆性质的(也就是随机的)称为 优先级 。不妨设优先级满足小根堆性质。 𝑥 𝑘 x k 𝑘 k 𝑋 𝑖 , 𝑗 X i , j { 𝑥 𝑖 , 𝑥 𝑖 + 1 , ⋯ , 𝑥 𝑗 − 1 , 𝑥 𝑗 } { x i , x i + 1 , ⋯ , x j − 1 , x j } 𝑖 i 𝑗 j d e p  ( 𝑥 ) dep  ( x ) 𝑥 x 0 0 𝑌 𝑖 , 𝑗 Y i , j 𝑥 𝑖 x i 𝑥 𝑗 x j 1 1 0 0 𝑌 𝑖 , 𝑖   = 0 Y i , i = 0 P r  ( 𝐴 ) Pr  ( A ) 𝐴 A 树高的证明 由于结点 𝑥 𝑖 x i 
d e p  ( 𝑥 𝑖 ) = 𝑛 ∑ 𝑘 = 1 𝑌 𝑘 , 𝑖 dep  ( x i ) = ∑ k = 1 n Y k , i 那么根据期望的线性性,有
𝐸 ( d e p  ( 𝑥 𝑖 ) ) = 𝐸 ( 𝑛 ∑ 𝑘 = 1 𝑌 𝑘 , 𝑖 ) = 𝑛 ∑ 𝑘 = 1 𝐸 ( 𝑌 𝑘 , 𝑖 ) E ( dep  ( x i ) ) = E ( ∑ k = 1 n Y k , i ) = ∑ k = 1 n E ( Y k , i ) 由于 𝑌 𝑘 , 𝑖 Y k , i 1 1 
𝐸 ( d e p  ( 𝑥 𝑖 ) ) = 𝑛 ∑ 𝑘 = 1 P r  ( 𝑌 𝑘 , 𝑖 = 1 ) E ( dep  ( x i ) ) = ∑ k = 1 n Pr  ( Y k , i = 1 ) 我们先证明引理:𝑌 𝑖 , 𝑗   = 1 Y i , j = 1 𝑥 𝑖 x i 𝑋 𝑖 , 𝑗 X i , j 
引理的证明  证明 :考虑分类讨论 𝑥 𝑖 x i 𝑥 𝑗 x j 
 若 𝑥 𝑖 x i 𝑥 𝑖 x i 𝑥 𝑗 x j 𝑥 𝑖 x i 𝑥 𝑗 x j  若 𝑥 𝑗 x j 𝑥 𝑗 x j 𝑥 𝑖 x i 𝑋 𝑖 , 𝑗 X i , j 𝑥 𝑖 x i 𝑥 𝑗 x j  若 𝑥 𝑖 x i 𝑥 𝑗 x j 𝑟   ∈ 𝑋 𝑖 , 𝑗 r ∈ X i , j 𝑥 𝑖 x i 𝑋 𝑖 , 𝑗 X i , j 𝑥 𝑖 x i 𝑥 𝑗 x j 𝑥 𝑖 x i 𝑥 𝑗 x j  若 𝑥 𝑖 x i 𝑥 𝑗 x j ◻ ◻  那么根据引理,深度的期望可以转化成
𝐸 ( d e p  ( 𝑥 𝑖 ) ) = 𝑛 ∑ 𝑘 = 1 P r  ( 𝑥 𝑘 = m i n 𝑋 𝑖 , 𝑘 ∧ 𝑘 ≠ 𝑖 ) E ( dep  ( x i ) ) = ∑ k = 1 n Pr  ( x k = min X i , k ∧ k ≠ i ) 又因为结点的优先级是随机的,我们假定集合 𝑋 𝑖 , 𝑗 X i , j 
𝐸 ( d e p  ( 𝑥 𝑖 ) ) = 𝑛 ∑ 𝑘 = 1 P r  ( 𝑥 𝑘 = m i n 𝑋 𝑖 , 𝑘 ∧ 𝑘 ≠ 𝑖 ) = 𝑛 ∑ 𝑘 = 1 P r  ( 𝑥 𝑘 = m i n 𝑋 𝑖 , 𝑘 ) − 1 = 𝑛 ∑ 𝑘 = 1 1 | 𝑖 − 𝑘 | + 1 − 1 = 𝑖 − 1 ∑ 𝑘 = 1 1 𝑖 − 𝑘 + 1 + 𝑛 ∑ 𝑘 = 𝑖 + 1 1 𝑘 − 𝑖 + 1 = 𝑖 ∑ 𝑗 = 2 1 𝑗 + 𝑛 − 𝑖 + 1 ∑ 𝑗 = 2 1 𝑗 ≤ 2 𝑛 ∑ 𝑗 = 2 1 𝑗 < 2 𝑛 ∑ 𝑗 = 2 ∫ 𝑗 𝑗 − 1 1 𝑥 d 𝑥 = 2 ∫ 𝑛 1 1 𝑥 d 𝑥 = 2 l n  𝑛 = 𝑂 ( l o g  𝑛 ) E ( dep  ( x i ) ) = ∑ k = 1 n Pr  ( x k = min X i , k ∧ k ≠ i ) = ∑ k = 1 n Pr  ( x k = min X i , k ) − 1 = ∑ k = 1 n 1 | i − k | + 1 − 1 = ∑ k = 1 i − 1 1 i − k + 1 + ∑ k = i + 1 n 1 k − i + 1 = ∑ j = 2 i 1 j + ∑ j = 2 n − i + 1 1 j ≤ 2 ∑ j = 2 n 1 j < 2 ∑ j = 2 n ∫ j − 1 j 1 x d x = 2 ∫ 1 n 1 x d x = 2 ln  n = O ( log  n ) 因此每个结点的期望高度都是 𝑂 ( l o g  𝑛 ) O ( log  n ) 
而朴素的二叉搜索树的操作的复杂度均是 𝑂 ( ℎ ) O ( h ) 𝑂 ( ℎ ) O ( h ) 𝑂 ( l o g  𝑛 ) O ( log  n ) 
期望复杂度的感性理解  首先,我们需要认识到一个节点的 𝑝 𝑟 𝑖 𝑜 𝑟 𝑖 𝑡 𝑦 priority 
 子节点值(𝑝 𝑟 𝑖 𝑜 𝑟 𝑖 𝑡 𝑦 priority  我们发现层数低的节点,比如整个树的根节点,它的 𝑝 𝑟 𝑖 𝑜 𝑟 𝑖 𝑡 𝑦 priority 𝑝 𝑟 𝑖 𝑜 𝑟 𝑖 𝑡 𝑦 priority 𝑝 𝑟 𝑖 𝑜 𝑟 𝑖 𝑡 𝑦 priority 
给 treap 插入新节点时,需要同时维护树和堆的性质。其中,搜索树的性质可以在插入时维护,而堆性质的维护则有两种处理方法,分别是旋转和分裂、合并。使用这两种方法的 treap 被分别称为 旋转 treap  和 无旋 treap 。
旋转 treap 旋转 treap  维护平衡的方式为旋转,和 AVL 树的旋转操作类似,分为 左旋  和 右旋 。即在满足二叉搜索树的条件下根据堆的优先级对 treap 进行平衡操作。
旋转 treap 在做普通平衡树题的时候,是所有平衡树中常数较小的。
下面的讲解中的代码用指针实现了旋转 treap,文末附有数组形式的完整实现。
Info  代码中的 rank 代表前面讲的优先级(𝑝 𝑟 𝑖 𝑜 𝑟 𝑖 𝑡 𝑦 priority 
节点结构 
 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 struct   Node   { 
   Node   * ch [ 2 ];    // 两个子节点的地址 
   int   val ,   rank ; 
   int   rep_cnt ;    // 当前这个值(val)重复出现的次数 
   int   siz ;        // 以当前节点为根的子树大小 
   Node ( int   val )   :   val ( val ),   rep_cnt ( 1 ),   siz ( 1 )   { 
     ch [ 0 ]   =   ch [ 1 ]   =   nullptr ; 
     rank   =   rand (); 
     // 注意初始化的时候,rank 是随机给出的 
   } 
   void   upd_siz ()   { 
     // 用于旋转和删除过后,重新计算 siz 的值 
     siz   =   rep_cnt ; 
     if   ( ch [ 0 ]   !=   nullptr )   siz   +=   ch [ 0 ] -> siz ; 
     if   ( ch [ 1 ]   !=   nullptr )   siz   +=   ch [ 1 ] -> siz ; 
   } 
}; 
旋转 旋转操作是 treap 的一个非常重要的操作,主要用来在保持 treap 树性质的同时,调整不同节点的层数,以达到维护堆性质的作用。
旋转操作的左旋和右旋可能不是特别容易区分,以下是两个较为明显的特点:
旋转操作的含义:
在不影响搜索树性质的前提下,把和旋转方向相反的子树变成根节点(如左旋,就是把右子树变成根节点) 不影响性质,并且在旋转过后,跟旋转方向相同的子节点变成了原来的根节点(如左旋,旋转完之后的左子节点是旋转前的根节点) 左旋和右旋操作是相互的,如下图。
 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 enum   rot_type   {   LF   =   1 ,   RT   =   0   }; 
void   _rotate ( Node   *& cur , 
              rot_type   dir )   {    // dir参数代表旋转的方向 0为右旋,1为左旋 
   // 注意传进来的 cur 是指针的引用,也就是改了这个 
   // cur,变量是跟着一起改的,如果这个 cur 是别的 树的子节点,根据 ch 
   // 找过来的时候,也是会找到这里的 
   // 以下的代码解释的均是左旋时的情况 
   Node   * tmp   =   cur -> ch [ dir ];    // 让 C 变成根节点, 
                              // 这里的 tmp 
                              // 是一个临时的节点指针,指向成为新的根节点的节点 
   /* 左旋:也就是让右子节点变成根节点 
   *         A                 C 
   *        / \               / \ 
   *       B  C    ---->     A   E 
   *         / \            / \ 
   *        D   E          B   D 
   */ 
   cur -> ch [ dir ]   =   tmp -> ch [ ! dir ];      // 让 A 的右子节点变成 D 
   tmp -> ch [ ! dir ]   =   cur ;               // 让 C 的左子节点变成 A 
   cur -> upd_siz (),   tmp -> upd_siz ();    // 更新大小信息 
   cur   =   tmp ;    // 最后把临时储存 C 树的变量赋值到当前根节点上(注意 cur 是引用) 
} 
插入 类似普通二叉搜索树的插入,但是需要在插入的过程中通过旋转来维护优先级的堆性质。
 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 void   _insert ( Node   *& cur ,   int   val )   { 
   if   ( cur   ==   nullptr )   { 
     // 没这个节点直接新建 
     cur   =   new   Node ( val ); 
     return ; 
   }   else   if   ( val   ==   cur -> val )   { 
     // 如果有这个值相同的节点,就把重复数量加一 
     cur -> rep_cnt ++ ; 
     cur -> siz ++ ; 
   }   else   if   ( val   <   cur -> val )   { 
     // 维护搜索树性质,val 比当前节点小就插到左边,反之亦然 
     _insert ( cur -> ch [ 0 ],   val ); 
     if   ( cur -> ch [ 0 ] -> rank   <   cur -> rank )   { 
       // 小根堆中,上面节点的优先级一定更小 
       // 因为新插的左子节点比父节点小,现在需要让左子节点变成父节点 
       _rotate ( cur ,   RT );    // 注意前面的旋转性质,要把左子节点转上来,需要右旋 
     } 
     cur -> upd_siz ();    // 插入之后大小会变化,需要更新 
   }   else   { 
     _insert ( cur -> ch [ 1 ],   val ); 
     if   ( cur -> ch [ 1 ] -> rank   <   cur -> rank )   { 
       _rotate ( cur ,   LF ); 
     } 
     cur -> upd_siz (); 
   } 
} 
删除 主要就是分类讨论,不同的情况有不同的处理方法,删完了树的大小会有变化,要注意更新。并且如果要删的节点有左子树和右子树,就要考虑删除之后让谁来当父节点(维护 rank 小的节点在上面)。
 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
37 
38 
39 
40 
41 
42 
43 
44 
45 
46 
47 
48 
49 
50 
51 
52 
53 void   _del ( Node   *& cur ,   int   val )   { 
   if   ( val   >   cur -> val )   { 
     _del ( cur -> ch [ 1 ],   val ); 
     // 值更大就在右子树,反之亦然 
     cur -> upd_siz (); 
   }   else   if   ( val   <   cur -> val )   { 
     _del ( cur -> ch [ 0 ],   val ); 
     cur -> upd_siz (); 
   }   else   { 
     if   ( cur -> rep_cnt   >   1 )   { 
       // 如果要删除的节点是重复的,可以直接把重复值减小 
       cur -> rep_cnt -- ,   cur -> siz -- ; 
       return ; 
     } 
     uint8_t   state   =   0 ; 
     state   |=   ( cur -> ch [ 0 ]   !=   nullptr ); 
     state   |=   (( cur -> ch [ 1 ]   !=   nullptr )   <<   1 ); 
     // 00都无,01有左无右,10,无左有右,11都有 
     Node   * tmp   =   cur ; 
     switch   ( state )   { 
       case   0 : 
         delete   cur ; 
         cur   =   nullptr ; 
         // 没有任何子节点,就直接把这个节点删了 
         break ; 
       case   1 :    // 有左无右 
         cur   =   tmp -> ch [ 0 ]; 
         // 把根变成左儿子,然后把原来的根节删了,注意这里的 tmp 是从 cur 
         // 复制的,而 cur 是引用 
         delete   tmp ; 
         break ; 
       case   2 :    // 有右无左 
         cur   =   tmp -> ch [ 1 ]; 
         delete   tmp ; 
         break ; 
       case   3 : 
         rot_type   dir   =   cur -> ch [ 0 ] -> rank   <   cur -> ch [ 1 ] -> rank 
                            ?   RT 
                            :   LF ;    // dir 是 rank 更小的那个儿子 
         _rotate ( cur ,   dir );    // 这里的旋转可以把优先级更小的儿子转上去,rt 是 0, 
                             // 而 lf 是 1,刚好跟实际的子树下标反过来 
         _del ( 
             cur -> ch [ ! dir ], 
             val );    // 旋转完成后原来的根节点就在旋方向那边,所以需要 
                    // 继续把这个原来的根节点删掉 
                    // 如果说要删的这个节点是在整个树的「上层的」,那我们会一直通过这 
                    // 这里的旋转操作,把它转到没有子树了(或者只有一个),再删掉它。 
         cur -> upd_siz (); 
         // 删除会造成大小改变 
         break ; 
     } 
   } 
} 
根据值查询排名 操作含义:查询以 cur 为根节点的子树中,val 这个值的大小的排名(该子树中小于 val 的节点的个数 + 1)
 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 int   _query_rank ( Node   * cur ,   int   val )   { 
   int   less_siz   =   cur -> ch [ 0 ]   ==   nullptr   ?   0   :   cur -> ch [ 0 ] -> siz ; 
   // 这个树中小于 val 的节点的数量 
   if   ( val   ==   cur -> val ) 
     // 如果这个节点就是要查的节点 
     return   less_siz   +   1 ; 
   else   if   ( val   <   cur -> val )   { 
     if   ( cur -> ch [ 0 ]   !=   nullptr ) 
       return   _query_rank ( cur -> ch [ 0 ],   val ); 
     else 
       return   1 ;    // 如果左子树是空的,说比最小的节点还要小,那这个数字就是最小的 
   }   else   { 
     if   ( cur -> ch [ 1 ]   !=   nullptr ) 
       // 如果要查的值比这个节点大,那这个节点的左子树以及这个节点自身肯定都比要查的值小 
       // 所以要加上这两个值,再加上往右边找的结果 
       // (以右子树为根的子树中,val 这个值的大小的排名) 
       return   less_siz   +   cur -> rep_cnt   +   _query_rank ( cur -> ch [ 1 ],   val ); 
     else 
       return   cur -> siz   +   1 ; 
     // 没有右子树的话直接整个树 + 1 相当于 less_siz + cur->rep_cnt + 1 
   } 
} 
根据排名查询值 要根据排名查询值,我们首先要知道如何判断要查的节点在树的哪个部分:
以下是一个判断方法的表:
左子树 根节点/当前节点 右子树 排名 ≤ 左子树的大小 排名 > 左子树的大小,并且 ≤ 左子树的大小 + 根节点的重复次数 排名 > 左子树的大小 + 根节点的重复次数 
注意如果在右子树,递归的时候需要对原来的 rank 进行处理。递归的时候就相当去查,在右子树中为这个排名的值,为了把排名转换成基于右子树的,需要把原来的 rank 减去左子树的大小和根节点的重复次数。
可以把所有节点想象成一个排好序的数组,或者数轴(如下),
1 -> |左子树的节点|根节点|右子树的节点| -> n
                           ^
                           要查的排名
                     ⬇转换成基于右子树的排名
1 -> |右子树的节点| -> n
       ^
       要查的排名
这里的转换方法就是直接把排名减去左子树的大小和根节点的重复数量。
int   _query_val ( Node   * cur ,   int   rank )   { 
   // 查询树中第 rank 大的节点的值 
   int   less_siz   =   cur -> ch [ 0 ]   ==   nullptr   ?   0   :   cur -> ch [ 0 ] -> siz ; 
   // less siz 是左子树的大小 
   if   ( rank   <=   less_siz ) 
     return   _query_val ( cur -> ch [ 0 ],   rank ); 
   else   if   ( rank   <=   less_siz   +   cur -> rep_cnt ) 
     return   cur -> val ; 
   else 
     return   _query_val ( cur -> ch [ 1 ],   rank   -   less_siz   -   cur -> rep_cnt );    // 见前文 
} 
查询第一个比 val 小的节点 注意这里使用了一个类中的全局变量,q_prev_tmp。
这个值是只有在 val 比当前节点值大的时候才会被更改的,所以返回这个变量就是返回 val 最后一次比当前节点的值大,之后就是更小了。
 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
10 
11 
12 
13 
14 
15 
16 int   _query_prev ( Node   * cur ,   int   val )   { 
   if   ( val   <=   cur -> val )   { 
     // 还是比 val 大,所以往左子树找 
     if   ( cur -> ch [ 0 ]   !=   nullptr )   return   _query_prev ( cur -> ch [ 0 ],   val ); 
   }   else   { 
     // 只有能进到这个 else 里,才会更新 q_prev_tmp 的值 
     q_prev_tmp   =   cur -> val ; 
     // 当前节点已经比 val,小了,但是不确定是否是最大的,所以要到右子树继续找 
     if   ( cur -> ch [ 1 ]   !=   nullptr )   _query_prev ( cur -> ch [ 1 ],   val ); 
     // 接下来的递归可能不会更改 q_prev_tmp 
     // 了,那就直接返回这个值,总之返回的就是最后一次进到 这个 else 中的 
     // cur->val 
     return   q_prev_tmp ; 
   } 
   return   NIL ; 
} 
查询第一个比 val 大的节点 跟前一个很相似,只是大于小于号换了一下。
int   _query_nex ( Node   * cur ,   int   val )   { 
   if   ( val   >=   cur -> val )   { 
     if   ( cur -> ch [ 1 ]   !=   nullptr )   return   _query_nex ( cur -> ch [ 1 ],   val ); 
   }   else   { 
     q_nex_tmp   =   cur -> val ; 
     if   ( cur -> ch [ 0 ]   !=   nullptr )   _query_nex ( cur -> ch [ 0 ],   val ); 
     return   q_nex_tmp ; 
   } 
   return   NIL ; 
} 
无旋 treap 无旋 treap 的操作方式使得它天生支持维护序列、可持久化等特性。
无旋 treap  又称分裂合并 treap。它仅有两种核心操作,即为 分裂  与 合并 。通过这两种操作,在很多情况下可以比旋转 treap 更方便的实现别的操作。下面逐一介绍这两种操作。
注释  讲解无旋 treap 应当提到 FHQ-Treap (by 范浩强)。即可持久化,支持区间操作的无旋 Treap。更多内容请参照《范浩强谈数据结构》ppt。
分裂(split) 按值分裂 分裂过程接受两个参数:根指针 𝑐 𝑢 𝑟 cur 𝑘 𝑒 𝑦 key 𝑣 𝑎 𝑙 val 𝑘 𝑒 𝑦 key 𝑘 𝑒 𝑦 key 
该过程首先判断 𝑘 𝑒 𝑦 key 𝑐 𝑢 𝑟 cur 𝑐 𝑢 𝑟 cur 𝑘 𝑒 𝑦 key 𝑘 𝑒 𝑦 key 𝑘 𝑒 𝑦 key 𝑐 𝑢 𝑟 cur 𝑐 𝑢 𝑟 cur 𝑘 𝑒 𝑦 key 
相应的,如果 𝑘 𝑒 𝑦 key 𝑐 𝑢 𝑟 cur 𝑐 𝑢 𝑟 cur 𝑘 𝑒 𝑦 key 𝑐 𝑢 𝑟 cur 𝑘 𝑒 𝑦 key 𝑘 𝑒 𝑦 key 𝑐 𝑢 𝑟 cur 𝑐 𝑢 𝑟 cur 𝑘 𝑒 𝑦 key 
下图展示了 𝑐 𝑢 𝑟 cur 𝑘 𝑒 𝑦 key 
 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 pair < Node   * ,   Node   *>   split ( Node   * cur ,   int   key )   { 
   if   ( cur   ==   nullptr )   return   { nullptr ,   nullptr }; 
   if   ( cur -> val   <=   key )   { 
     // cur 以及它的左子树一定属于分裂后的第一个树 
     auto   temp   =   split ( cur -> ch [ 1 ],   key ); 
     // 但是它可能有部分右子树也比 key 小 
     cur -> ch [ 1 ]   =   temp . first ; 
     // 我们把小于 key 的那部分拿出来,作为 cur 的右子树,这样整个 cur 都是小于 
     // key 的 剩下的那部分右子树成为分裂后的第二个 treap 
     cur -> upd_siz (); 
     // 分裂过后树的大小会变化,需要更新 
     return   { cur ,   temp . second }; 
   }   else   { 
     // 同上 
     auto   temp   =   split ( cur -> ch [ 0 ],   key ); 
     cur -> ch [ 0 ]   =   temp . second ; 
     cur -> upd_siz (); 
     return   { temp . first ,   cur }; 
   } 
} 
按排名分裂 比起按值分裂,这个操作更像是旋转 treap 中的根据排名(某个节点的排名是树中所有小于此节点值的节点的数量 + 1 + 1 
此函数接受两个参数,节点指针 𝑐 𝑢 𝑟 cur 𝑟 𝑘 rk 
其中,第一个 treap 中每个节点的排名都小于 𝑟 𝑘 rk 𝑟 𝑘 rk Node 结构体中的 cnt),第三个则是大于。
此操作的重点在于判断排名和 𝑐 𝑢 𝑟 cur 
并且,此操作的递归部分和按值分裂也非常相似,这里不赘述。
 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 tuple < Node   * ,   Node   * ,   Node   *>   split_by_rk ( Node   * cur ,   int   rk )   { 
   if   ( cur   ==   nullptr )   return   { nullptr ,   nullptr ,   nullptr }; 
   int   ls_siz   =   cur -> ch [ 0 ]   ==   nullptr   ?   0   :   cur -> ch [ 0 ] -> siz ; 
   if   ( rk   <=   ls_siz )   { 
     // 排名和 cur 相等的节点在左子树 
     Node   * l ,   * mid ,   * r ; 
     tie ( l ,   mid ,   r )   =   split_by_rk ( cur -> ch [ 0 ],   rk ); 
     cur -> ch [ 0 ]   =   r ;    // 返回的第三个 treap 中的排名都大于 rk 
     // cur 的左子树被设成 r 后,整个 cur 中节点的排名都大于 rk 
     cur -> upd_siz (); 
     return   { l ,   mid ,   cur }; 
   }   else   if   ( rk   <=   ls_siz   +   cur -> cnt )   { 
     // 和 cur 相等的就是当前节点 
     Node   * lt   =   cur -> ch [ 0 ]; 
     Node   * rt   =   cur -> ch [ 1 ]; 
     cur -> ch [ 0 ]   =   cur -> ch [ 1 ]   =   nullptr ; 
     // 分裂后第二个 treap 只有一个节点,所有要把它的子树设置为空 
     return   { lt ,   cur ,   rt }; 
   }   else   { 
     // 排名和 cur 相等的节点在右子树 
     // 递归过程同上 
     Node   * l ,   * mid ,   * r ; 
     tie ( l ,   mid ,   r )   =   split_by_rk ( cur -> ch [ 1 ],   rk   -   ls_siz   -   cur -> cnt ); 
     cur -> ch [ 1 ]   =   l ; 
     cur -> upd_siz (); 
     return   { cur ,   mid ,   r }; 
   } 
} 
合并(merge) 合并过程接受两个参数:左 treap 的根指针 𝑢 u 𝑣 v 𝑢 u 𝑣 v 𝑢 u 𝑣 v 
在旋转 treap 中,我们借助旋转操作来维护 𝑝 𝑟 𝑖 𝑜 𝑟 𝑖 𝑡 𝑦 priority 
因为两个 treap 已经有序,所以我们在合并的时候只需要考虑把哪个树「放在上面」,把哪个「放在下面」,也就是是需要判断将哪个一个树作为子树。显然,根据堆的性质,我们需要把 𝑝 𝑟 𝑖 𝑜 𝑟 𝑖 𝑡 𝑦 priority 
同时,我们还需要满足搜索树的性质,所以若 𝑢 u 𝑝 𝑟 𝑖 𝑜 𝑟 𝑖 𝑡 𝑦 priority 𝑣 v 𝑢 u 𝑣 v 𝑢 u 𝑢 u 𝑣 v 𝑢 u 𝑣 v 𝑣 v 
 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 Node   * merge ( Node   * u ,   Node   * v )   { 
   // 传进来的两个树的内部已经符合搜索树的性质了 
   // 并且 u 内所有节点的值 < v 内所有节点的值 
   // 所以在合并的时候需要维护堆的性质 
   // 这里用的是小根堆 
   if   ( u   ==   nullptr   &&   v   ==   nullptr )   return   nullptr ; 
   if   ( u   !=   nullptr   &&   v   ==   nullptr )   return   u ; 
   if   ( v   !=   nullptr   &&   u   ==   nullptr )   return   v ; 
   if   ( u -> prio   <   v -> prio )   { 
     // u 的 prio 比较小,u应该作为父节点 
     u -> ch [ 1 ]   =   merge ( u -> ch [ 1 ],   v ); 
     // 因为 v 比 u 大,所以把 v 作为 u 的右子树 
     u -> upd_siz (); 
     return   u ; 
   }   else   { 
     // v 比较小,v应该作为父节点 
     v -> ch [ 0 ]   =   merge ( u ,   v -> ch [ 0 ]); 
     // u 比 v 小,所以递归时的参数是这样的 
     v -> upd_siz (); 
     return   v ; 
   } 
} 
插入 在无旋 treap 中,插入,删除,根据值查询排名等基础操作既可以用普通二叉查找树的方法实现,也可以用分裂和合并来实现。通常来说,使用分裂和合并来实现更加简洁,但是速度会慢一点
在实现插入操作时,我们利用了分裂操作的一些性质。也就是值小于等于 𝑣 𝑎 𝑙 val 
所以,假设我们根据 𝑣 𝑎 𝑙 val 
𝑇 1 ≤ 𝑣 𝑎 𝑙 𝑇 2 > 𝑣 𝑎 𝑙 T 1 ≤ v a l T 2 > v a l 其中 𝑇 1 T 1 𝑇 2 T 2 
如果我们再按照 𝑣 𝑎 𝑙   − 1 val − 1 𝑇 1 T 1 
𝑇 1   l e f t ≤ 𝑣 𝑎 𝑙 − 1 𝑇 1   r i g h t > 𝑣 𝑎 𝑙 − 1   &   𝑇 1   r i g h t ≤ 𝑣 𝑎 𝑙 T 1   left ≤ v a l − 1 T 1   right > v a l − 1   &   T 1   right ≤ v a l 其中 𝑇 1   l e f t T 1   left 𝑇 1 T 1 𝑇 1   r i g h t T 1   right &   𝑇 1   r i g h t   ≤ 𝑣 𝑎 𝑙 &   T 1   right ≤ v a l 𝑇 1 T 1 𝑇 1   ≤ 𝑣 𝑎 𝑙 T 1 ≤ v a l 
不难发现,只要 𝑣 𝑎 𝑙 val 𝑇 1   r i g h t T 1   right 𝑣 𝑎 𝑙 val 
在插入时,如果我们发现符合 𝑇 1   r i g h t T 1   right 
注意把树分裂好了还需要用合并操作把它「粘」回去,这样下次还能继续使用。并且,还需要注意合并操作的参数顺序是有要求的,第一个树的所有节点的值都需要小于第二个。
 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 void   insert ( int   val )   { 
   auto   temp   =   split ( root ,   val ); 
   // 根据 val 的值把整个树分成两个 
   // 注意 split 的实现,等于 val 的子树是在左子树的 
   auto   l_tr   =   split ( temp . first ,   val   -   1 ); 
   // l_tr 的左子树 <= val - 1,如果有 = val 的节点,那一定在右子树 
   Node   * new_node ; 
   if   ( l_tr . second   ==   nullptr )   { 
     // 没有这个节点就新开,否则直接增加重复次数。 
     new_node   =   new   Node ( val ); 
   }   else   { 
     l_tr . second -> cnt ++ ; 
     l_tr . second -> upd_siz (); 
   } 
   Node   * l_tr_combined   = 
       merge ( l_tr . first ,   l_tr . second   ==   nullptr   ?   new_node   :   l_tr . second ); 
   // 合并 T_1 left 和 T_1 right 
   root   =   merge ( l_tr_combined ,   temp . second ); 
   // 合并 T_1 和 T_2 
} 
删除 删除操作也使用和插入操作相似的方法,找到值和 𝑣 𝑎 𝑙 val 
 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
10 
11 
12 
13 
14 
15 
16 
17 
18 void   del ( int   val )   { 
   auto   temp   =   split ( root ,   val ); 
   auto   l_tr   =   split ( temp . first ,   val   -   1 ); 
   if   ( l_tr . second -> cnt   >   1 )   { 
     // 如果这个节点的重复次数大于 1,减小即可 
     l_tr . second -> cnt -- ; 
     l_tr . second -> upd_siz (); 
     l_tr . first   =   merge ( l_tr . first ,   l_tr . second ); 
   }   else   { 
     if   ( temp . first   ==   l_tr . second )   { 
       // 有可能整个 T_1 只有这个节点,所以也需要把这个点设成 null 来标注已经删除 
       temp . first   =   nullptr ; 
     } 
     delete   l_tr . second ; 
     l_tr . second   =   nullptr ; 
   } 
   root   =   merge ( l_tr . first ,   temp . second ); 
} 
根据值查询排名 排名是比这个值小的节点的数量 + 1 + 1 𝑣 𝑎 𝑙   − 1 val − 1 
𝑇 1 ≤ 𝑣 𝑎 𝑙 − 1 T 1 ≤ v a l − 1 如果树的值和 𝑣 𝑎 𝑙 val 𝑇 1 T 1 𝑣 𝑎 𝑙 val 
int   qrank_by_val ( Node *   cur ,   int   val )   { 
   auto   temp   =   split ( cur ,   val   -   1 ); 
   int   ret   =   ( temp . first   ==   nullptr   ?   0   :   temp . first -> siz )   +   1 ;    // 根据定义 + 1 
   root   =   merge ( temp . first ,   temp . second );    // 拆好了再粘回去 
   return   ret ; 
} 
根据排名查询值 调用 split_by_rk() 函数后,会返回分裂好的三个 treap,其中第二个只包含一个节点,它的排名等于 𝑟 𝑘 rk 𝑣 𝑎 𝑙 val 
int   qval_by_rank ( Node   * cur ,   int   rk )   { 
   Node   * l ,   * mid ,   * r ; 
   tie ( l ,   mid ,   r )   =   split_by_rk ( cur ,   rk ); 
   int   ret   =   mid -> val ; 
   root   =   merge ( merge ( l ,   mid ),   r ); 
   return   ret ; 
} 
查询第一个比 val 小的节点 可以把这个问题转化为,在比 𝑣 𝑎 𝑙 val 𝑣 𝑎 𝑙 val 𝑣 𝑎 𝑙 val qval_by_rank() 找出这个树中值最大的节点。
int   qprev ( int   val )   { 
   auto   temp   =   split ( root ,   val   -   1 ); 
   // temp.first 就是值小于 val 的子树 
   int   ret   =   qval_by_rank ( temp . first ,   temp . first -> siz ); 
   // 这里查询的是,所有小于 val 的节点里面,最大的那个的值 
   root   =   merge ( temp . first ,   temp . second ); 
   return   ret ; 
} 
查询第一个比 val 大的节点 和上个操作类似,可以把这个问题转化为,在比 𝑣 𝑎 𝑙 val 𝑣 𝑎 𝑙 val 𝑣 𝑎 𝑙 val 
然后我们去查询这个树中排名为 1 1 𝑣 𝑎 𝑙 val 
int   qnex ( int   val )   { 
   auto   temp   =   split ( root ,   val ); 
   int   ret   =   qval_by_rank ( temp . second ,   1 ); 
   // 查询所有大于 val 的子树里面,值最小的那个 
   root   =   merge ( temp . first ,   temp . second ); 
   return   ret ; 
} 
建树(build) 将一个有 𝑛 n { 𝑎 𝑛 } { a n } 
可以依次暴力插入这 𝑛 n 𝑣 v 𝑣 v 𝑣 v 𝑣 v 𝑂 ( l o g  𝑛 ) O ( log  n ) 𝑂 ( 𝑛 l o g  𝑛 ) O ( n log  n ) 
在某些题目内,可能会有多次插入一段有序序列的操作,这是就需要在 𝑂 ( 𝑛 ) O ( n ) 
方法一:在递归建树的过程中,每次选取当前区间的中点作为该区间的树根,并对每个节点钦定合适的优先值,使得新树满足堆的性质。这样能保证树高为 𝑂 ( l o g  𝑛 ) O ( log  n ) 
方法二:在递归建树的过程中,每次选取当前区间的中点作为该区间的树根,然后给每个节点一个随机优先级。这样能保证树高为 𝑂 ( l o g  𝑛 ) O ( log  n ) merge 操作更加随机一点,而不是用来保证树高的。
方法三:观察到 treap 是笛卡尔树,利用笛卡尔树的 𝑂 ( 𝑛 ) O ( n ) 
无旋 treap 的区间操作 建树 无旋 treap 相比旋转 treap 的一大好处就是可以实现各种区间操作,下面我们以文艺平衡树的 模板题  为例,介绍 treap 的区间操作。
您需要写一种数据结构(可参考题目标题),来维护一个有序数列。
其中需要提供以下操作:翻转一个区间,例如原有序序列是 5   4   3   2   1 5   4   3   2   1 [ 2 , 4 ] [ 2 , 4 ] 5   2   3   4   1 5   2   3   4   1 1 0 0 % 100 % 1   ≤ 𝑛 1 ≤ n 𝑚 m ≤ 1 0 5 ≤ 10 5 
在这道题目中,我们需要实现的是区间翻转,那么我们首先需要考虑如何建树,建出来的树需要是初始的区间。
我们只需要把区间的下标依次插入 treap 中,这样在中序遍历(先遍历左子树,然后当前节点,最后右子树)时,就可以得到这个区间
我们知道在朴素的二叉查找树中按照递增的顺序插入节点,建出来的树是一个长链,按照中序遍历,自然可以得到这个区间。
如上图,按照 1   2   3   4   5 1   2   3   4   5 1   2   3   4   5 1   2   3   4   5 
但是在 treap 中,按增序插入节点后,在合并操作时还会根据 𝑝 𝑟 𝑖 𝑜 𝑟 𝑖 𝑡 𝑦 priority 
可以参考 笛卡尔树的单调栈建树方法  来理解这个问题。
设新插入的节点为 𝑢 u 
首先,因为是递增地插入节点,每一个新插入的节点肯定会被连接到 treap 的右链(即从根结点一直往右子树走,经过的结点形成的链)上。
从根节点开始,右链上的节点的 𝑝 𝑟 𝑖 𝑜 𝑟 𝑖 𝑡 𝑦 priority 𝑝 𝑟 𝑖 𝑜 𝑟 𝑖 𝑡 𝑦 priority 𝑢 u 𝑣 v 𝑢 u 
因为 𝑢 u 𝑣 v 𝑢 u 𝑢 u 
可以发现,中序遍历时 𝑢 u 𝑢 u 
下图是一个 treap 根据递增顺序插入 1   ∼ 5 1 ∼ 5 5 5 
区间翻转 翻转 [ 𝑙 , 𝑟 ] [ l , r ] [ 1 , 𝑙   − 1 ] ,   [ 𝑙 , 𝑟 ] ,   [ 𝑟   + 1 , 𝑛 ] [ 1 , l − 1 ] ,   [ l , r ] ,   [ r + 1 , n ] [ 𝑙 , 𝑟 ] [ l , r ] 
翻转的具体操作是把区间内的子树的每一个左,右子节点交换位置。如下图就展示了翻转上图中 treap 的 [ 3 , 4 ] [ 3 , 4 ] [ 3 , 5 ] [ 3 , 5 ] 
注意如果按照这个方法翻转,那么每次翻转 [ 𝑙 , 𝑟 ] [ l , r ] 𝑟   − 𝑙 r − l 1 0 5 10 5 𝑂 ( 𝑛   × l o g 2  𝑛 ) O ( n × log 2  n ) 𝑂 ( l o g 2  𝑛 ) O ( log 2  n ) 
再观察题目要求,可以发现因为只需要最后输出操作完的区间,所以并不需要每次都真的去交换。如此一来,便可以使用线段树中常用的懒标记(lazy tag)来优化复杂度。交换时,只需要在父节点打上标记,代表这个子树下的每个左右子节点都需要交换就行了。
在线段树中,我们一般在更新和查询时下传懒标记。这是因为,在更新和查询时,我们想要更新/查询的范围不一定和懒标记代表的范围重合,所以要先下传标记,确保查到和更新后的值是正确的。
在无旋 treap 中也是一样。具体操作时我们会把 treap 分裂成前文讲到的三个树,然后给中间的树打上懒标记后合并这三棵树。因为我们想要翻转的区间和懒标记代表的区间不一定重合,所以要在分裂时下传标记。并且,分裂和合并操作会造成每个节点及其懒标记所代表的节点发生变动,所以也需要在合并前下传懒标记。
换句话说,是当树的结构发生改变的时候,当我们进行分裂或合并操作时需要改变某一个点的左右儿子信息时之前,应该下放标记,而非之后,因为懒标记是需要下传给儿子节点的,但更改左右儿子信息之后若懒标记还未下放,则懒标记就丢失了下放的对象。
以下为代码讲解,代码参考了
因为区间操作中大部分操作都和普通的无旋 treap 相同,所以这里只讲解和普通无旋 treap 不同的地方。
下传标记 需要注意这里的懒标记代表需要把这个树中的每一个子节点交换位置。所以如果当前节点的子节点也有懒标记,那两次翻转就抵消了。如果子节点不需要翻转,那么这个懒标记就需要继续被下传到子节点上。
// 这里这个 pushdown 是 Node 类的成员函数,其中 to_rev 是懒标记 
void   pushdown ()   { 
   swap ( ch [ 0 ],   ch [ 1 ]); 
   if   ( ch [ 0 ]   !=   nullptr )   ch [ 0 ] -> to_rev   ^=   1 ; 
   if   ( ch [ 1 ]   !=   nullptr )   ch [ 1 ] -> to_rev   ^=   1 ; 
   to_rev   =   false ; 
} 
void   check_tag ()   { 
   if   ( to_rev )   pushdown (); 
} 
分裂 注意在这个题目中,因为翻转操作,treap 中的 𝑣 𝑎 𝑙 val 𝑣 𝑎 𝑙 val 
所以这里的分裂跟普通无旋 treap 中的按排名分裂更相似,是根据当前树的大小判断往左还是右子树递归的,换言之,我们是按照开始时这个节点在树中的位置来判断的。
返回的第一个 treap 中节点的排名全部小于等于 𝑠 𝑧 sz 𝑠 𝑧 sz 
 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 #define siz(_) (_ == nullptr ? 0 : _->siz) 
pair < Node * ,   Node *>   split ( Node *   cur ,   int   sz )   { 
   // 按照树的大小判断 
   if   ( cur   ==   nullptr )   return   { nullptr ,   nullptr }; 
   cur -> check_tag (); 
   // 分裂前先下传 
   if   ( sz   <=   siz ( cur -> ch [ 0 ]))   { 
     auto   temp   =   split ( cur -> ch [ 0 ],   sz ); 
     cur -> ch [ 0 ]   =   temp . second ; 
     cur -> upd_siz (); 
     return   { temp . first ,   cur }; 
   }   else   { 
     auto   temp   = 
         split ( cur -> ch [ 1 ], 
               sz   -   siz ( cur -> ch [ 0 ])   - 
                   1 );    // 这里的转换在有旋 treap 的 「根据排名查询值有讲」 
     cur -> ch [ 1 ]   =   temp . first ; 
     cur -> upd_siz (); 
     return   { cur ,   temp . second }; 
   } 
} 
合并 唯一需要注意的是在合并前下传懒标记
 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
10 
11 
12 
13 
14 
15 
16 Node   * merge ( Node   * sm ,   Node   * bg )   { 
   // small, big 
   if   ( sm   ==   nullptr   &&   bg   ==   nullptr )   return   nullptr ; 
   if   ( sm   !=   nullptr   &&   bg   ==   nullptr )   return   sm ; 
   if   ( sm   ==   nullptr   &&   bg   !=   nullptr )   return   bg ; 
   sm -> check_tag (),   bg -> check_tag (); 
   if   ( sm -> prio   <   bg -> prio )   { 
     sm -> ch [ 1 ]   =   merge ( sm -> ch [ 1 ],   bg ); 
     sm -> upd_siz (); 
     return   sm ; 
   }   else   { 
     bg -> ch [ 0 ]   =   merge ( sm ,   bg -> ch [ 0 ]); 
     bg -> upd_siz (); 
     return   bg ; 
   } 
} 
区间翻转 和前面介绍的一样,分裂出 [ 1 , 𝑙   − 1 ] ,   [ 𝑙 , 𝑟 ] ,   [ 𝑟   + 1 , 𝑛 ] [ 1 , l − 1 ] ,   [ l , r ] ,   [ r + 1 , n ] 
void   seg_rev ( int   l ,   int   r )   { 
   // 这里的 less 和 more 是相对于 l 的 
   auto   less   =   split ( root ,   l   -   1 ); 
   // 所有小于等于 l - 1 的会在 less 的左子树 
   auto   more   =   split ( less . second ,   r   -   l   +   1 ); 
   // 从 l 开始的前 r - l + 1 个元素的区间 
   more . first -> to_rev   =   true ; 
   root   =   merge ( less . first ,   merge ( more . first ,   more . second )); 
} 
中序遍历打印 要注意在打印时要下传标记。
void   print ( Node *   cur )   { 
   if   ( cur   ==   nullptr )   return ; 
   cur -> check_tag (); 
   // 中序遍历 -> 先左子树,再自己,最后右子树 
   print ( cur -> ch [ 0 ]); 
   cout   <<   cur -> val   <<   " " ; 
   print ( cur -> ch [ 1 ]); 
} 
完整代码 旋转 treap 指针实现 完整代码  以下是前文讲解的代码的完整版本,是普通平衡树的模板代码。
   1 
  2 
  3 
  4 
  5 
  6 
  7 
  8 
  9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
 42 
 43 
 44 
 45 
 46 
 47 
 48 
 49 
 50 
 51 
 52 
 53 
 54 
 55 
 56 
 57 
 58 
 59 
 60 
 61 
 62 
 63 
 64 
 65 
 66 
 67 
 68 
 69 
 70 
 71 
 72 
 73 
 74 
 75 
 76 
 77 
 78 
 79 
 80 
 81 
 82 
 83 
 84 
 85 
 86 
 87 
 88 
 89 
 90 
 91 
 92 
 93 
 94 
 95 
 96 
 97 
 98 
 99 
100 
101 
102 
103 
104 
105 
106 
107 
108 
109 
110 
111 
112 
113 
114 
115 
116 
117 
118 
119 
120 
121 
122 
123 
124 
125 
126 
127 
128 
129 
130 
131 
132 
133 
134 
135 
136 
137 
138 
139 
140 
141 
142 
143 
144 
145 
146 
147 
148 
149 
150 
151 
152 
153 
154 
155 
156 
157 
158 
159 
160 
161 
162 
163 
164 
165 
166 
167 
168 
169 
170 
171 
172 
173 
174 
175 
176 
177 
178 
179 
180 
181 
182 
183 
184 
185 
186 
187 
188 
189 
190 
191 
192 
193 
194 
195 
196 
197 
198 
199 // author: (ttzytt)[ttzytt.com] 
#include   <cstdint> 
#include   <cstdio> 
#include   <cstdlib> 
using   namespace   std ; 
struct   Node   { 
   Node   * ch [ 2 ]; 
   int   val ,   rank ; 
   int   rep_cnt ; 
   int   siz ; 
   Node ( int   val )   :   val ( val ),   rep_cnt ( 1 ),   siz ( 1 )   { 
     ch [ 0 ]   =   ch [ 1 ]   =   nullptr ; 
     rank   =   rand (); 
   } 
   void   upd_siz ()   { 
     siz   =   rep_cnt ; 
     if   ( ch [ 0 ]   !=   nullptr )   siz   +=   ch [ 0 ] -> siz ; 
     if   ( ch [ 1 ]   !=   nullptr )   siz   +=   ch [ 1 ] -> siz ; 
   } 
}; 
class   Treap   { 
  private : 
   Node   * root ; 
   constexpr   static   int   NIL   =   -1 ;    // 用于表示查询的值不存在 
   enum   rot_type   {   LF   =   1 ,   RT   =   0   }; 
   int   q_prev_tmp   =   0 ,   q_nex_tmp   =   0 ; 
   void   _rotate ( Node   *& cur ,   rot_type   dir )   {    // 0为右旋,1为左旋 
     Node   * tmp   =   cur -> ch [ dir ]; 
     cur -> ch [ dir ]   =   tmp -> ch [ ! dir ]; 
     tmp -> ch [ ! dir ]   =   cur ; 
     cur -> upd_siz (),   tmp -> upd_siz (); 
     cur   =   tmp ; 
   } 
   void   _insert ( Node   *& cur ,   int   val )   { 
     if   ( cur   ==   nullptr )   { 
       cur   =   new   Node ( val ); 
       return ; 
     }   else   if   ( val   ==   cur -> val )   { 
       cur -> rep_cnt ++ ; 
       cur -> siz ++ ; 
     }   else   if   ( val   <   cur -> val )   { 
       _insert ( cur -> ch [ 0 ],   val ); 
       if   ( cur -> ch [ 0 ] -> rank   <   cur -> rank )   { 
         _rotate ( cur ,   RT ); 
       } 
       cur -> upd_siz (); 
     }   else   { 
       _insert ( cur -> ch [ 1 ],   val ); 
       if   ( cur -> ch [ 1 ] -> rank   <   cur -> rank )   { 
         _rotate ( cur ,   LF ); 
       } 
       cur -> upd_siz (); 
     } 
   } 
   void   _del ( Node   *& cur ,   int   val )   { 
     if   ( val   >   cur -> val )   { 
       _del ( cur -> ch [ 1 ],   val ); 
       cur -> upd_siz (); 
     }   else   if   ( val   <   cur -> val )   { 
       _del ( cur -> ch [ 0 ],   val ); 
       cur -> upd_siz (); 
     }   else   { 
       if   ( cur -> rep_cnt   >   1 )   { 
         cur -> rep_cnt -- ,   cur -> siz -- ; 
         return ; 
       } 
       uint8_t   state   =   0 ; 
       state   |=   ( cur -> ch [ 0 ]   !=   nullptr ); 
       state   |=   (( cur -> ch [ 1 ]   !=   nullptr )   <<   1 ); 
       // 00都无,01有左无右,10,无左有右,11都有 
       Node   * tmp   =   cur ; 
       switch   ( state )   { 
         case   0 : 
           delete   cur ; 
           cur   =   nullptr ; 
           break ; 
         case   1 :    // 有左无右 
           cur   =   tmp -> ch [ 0 ]; 
           delete   tmp ; 
           break ; 
         case   2 :    // 有右无左 
           cur   =   tmp -> ch [ 1 ]; 
           delete   tmp ; 
           break ; 
         case   3 : 
           rot_type   dir   =   cur -> ch [ 0 ] -> rank   <   cur -> ch [ 1 ] -> rank   ?   RT   :   LF ; 
           _rotate ( cur ,   dir ); 
           _del ( cur -> ch [ ! dir ],   val ); 
           cur -> upd_siz (); 
           break ; 
       } 
     } 
   } 
   int   _query_rank ( Node   * cur ,   int   val )   { 
     int   less_siz   =   cur -> ch [ 0 ]   ==   nullptr   ?   0   :   cur -> ch [ 0 ] -> siz ; 
     if   ( val   ==   cur -> val ) 
       return   less_siz   +   1 ; 
     else   if   ( val   <   cur -> val )   { 
       if   ( cur -> ch [ 0 ]   !=   nullptr ) 
         return   _query_rank ( cur -> ch [ 0 ],   val ); 
       else 
         return   1 ; 
     }   else   { 
       if   ( cur -> ch [ 1 ]   !=   nullptr ) 
         return   less_siz   +   cur -> rep_cnt   +   _query_rank ( cur -> ch [ 1 ],   val ); 
       else 
         return   cur -> siz   +   1 ; 
     } 
   } 
   int   _query_val ( Node   * cur ,   int   rank )   { 
     int   less_siz   =   cur -> ch [ 0 ]   ==   nullptr   ?   0   :   cur -> ch [ 0 ] -> siz ; 
     if   ( rank   <=   less_siz ) 
       return   _query_val ( cur -> ch [ 0 ],   rank ); 
     else   if   ( rank   <=   less_siz   +   cur -> rep_cnt ) 
       return   cur -> val ; 
     else 
       return   _query_val ( cur -> ch [ 1 ],   rank   -   less_siz   -   cur -> rep_cnt ); 
   } 
   int   _query_prev ( Node   * cur ,   int   val )   { 
     if   ( val   <=   cur -> val )   { 
       if   ( cur -> ch [ 0 ]   !=   nullptr )   return   _query_prev ( cur -> ch [ 0 ],   val ); 
     }   else   { 
       q_prev_tmp   =   cur -> val ; 
       if   ( cur -> ch [ 1 ]   !=   nullptr )   _query_prev ( cur -> ch [ 1 ],   val ); 
       return   q_prev_tmp ; 
     } 
     return   NIL ; 
   } 
   int   _query_nex ( Node   * cur ,   int   val )   { 
     if   ( val   >=   cur -> val )   { 
       if   ( cur -> ch [ 1 ]   !=   nullptr )   return   _query_nex ( cur -> ch [ 1 ],   val ); 
     }   else   { 
       q_nex_tmp   =   cur -> val ; 
       if   ( cur -> ch [ 0 ]   !=   nullptr )   _query_nex ( cur -> ch [ 0 ],   val ); 
       return   q_nex_tmp ; 
     } 
     return   NIL ; 
   } 
  public : 
   void   insert ( int   val )   {   _insert ( root ,   val );   } 
   void   del ( int   val )   {   _del ( root ,   val );   } 
   int   query_rank ( int   val )   {   return   _query_rank ( root ,   val );   } 
   int   query_val ( int   rank )   {   return   _query_val ( root ,   rank );   } 
   int   query_prev ( int   val )   {   return   _query_prev ( root ,   val );   } 
   int   query_nex ( int   val )   {   return   _query_nex ( root ,   val );   } 
}; 
Treap   tr ; 
int   main ()   { 
   srand ( 0 ); 
   int   t ; 
   scanf ( "%d" ,   & t ); 
   while   ( t -- )   { 
     int   mode ; 
     int   num ; 
     scanf ( "%d%d" ,   & mode ,   & num ); 
     switch   ( mode )   { 
       case   1 : 
         tr . insert ( num ); 
         break ; 
       case   2 : 
         tr . del ( num ); 
         break ; 
       case   3 : 
         printf ( "%d \n " ,   tr . query_rank ( num )); 
         break ; 
       case   4 : 
         printf ( "%d \n " ,   tr . query_val ( num )); 
         break ; 
       case   5 : 
         printf ( "%d \n " ,   tr . query_prev ( num )); 
         break ; 
       case   6 : 
         printf ( "%d \n " ,   tr . query_nex ( num )); 
         break ; 
     } 
   } 
} 
数组实现 以下是 bzoj 普通平衡树模板代码,使用数组实现。
完整代码    1 
  2 
  3 
  4 
  5 
  6 
  7 
  8 
  9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
 42 
 43 
 44 
 45 
 46 
 47 
 48 
 49 
 50 
 51 
 52 
 53 
 54 
 55 
 56 
 57 
 58 
 59 
 60 
 61 
 62 
 63 
 64 
 65 
 66 
 67 
 68 
 69 
 70 
 71 
 72 
 73 
 74 
 75 
 76 
 77 
 78 
 79 
 80 
 81 
 82 
 83 
 84 
 85 
 86 
 87 
 88 
 89 
 90 
 91 
 92 
 93 
 94 
 95 
 96 
 97 
 98 
 99 
100 
101 
102 
103 
104 
105 
106 
107 
108 
109 
110 
111 
112 
113 
114 
115 
116 
117 
118 
119 
120 
121 
122 
123 
124 
125 
126 
127 
128 
129 
130 
131 
132 
133 
134 
135 
136 
137 
138 
139 
140 
141 
142 
143 
144 
145 
146 
147 
148 
149 #include   <iostream> 
constexpr   int   MAXN   =   100005 ; 
constexpr   int   INF   =   1   <<   30 ; 
int   n ; 
struct   treap   {    // 直接维护成数据结构,可以直接用 
   int   l [ MAXN ],   r [ MAXN ],   val [ MAXN ],   rnd [ MAXN ],   size_ [ MAXN ],   w [ MAXN ]; 
   int   sz ,   ans ,   rt ; 
   void   pushup ( int   x )   {   size_ [ x ]   =   size_ [ l [ x ]]   +   size_ [ r [ x ]]   +   w [ x ];   } 
   void   lrotate ( int   & k )   { 
     int   t   =   r [ k ]; 
     r [ k ]   =   l [ t ]; 
     l [ t ]   =   k ; 
     size_ [ t ]   =   size_ [ k ]; 
     pushup ( k ); 
     k   =   t ; 
   } 
   void   rrotate ( int   & k )   { 
     int   t   =   l [ k ]; 
     l [ k ]   =   r [ t ]; 
     r [ t ]   =   k ; 
     size_ [ t ]   =   size_ [ k ]; 
     pushup ( k ); 
     k   =   t ; 
   } 
   void   insert ( int   & k ,   int   x )   {    // 插入 
     if   ( ! k )   { 
       sz ++ ; 
       k   =   sz ; 
       size_ [ k ]   =   1 ; 
       w [ k ]   =   1 ; 
       val [ k ]   =   x ; 
       rnd [ k ]   =   rand (); 
       return ; 
     } 
     size_ [ k ] ++ ; 
     if   ( val [ k ]   ==   x )   { 
       w [ k ] ++ ; 
     }   else   if   ( val [ k ]   <   x )   { 
       insert ( r [ k ],   x ); 
       if   ( rnd [ r [ k ]]   <   rnd [ k ])   lrotate ( k ); 
     }   else   { 
       insert ( l [ k ],   x ); 
       if   ( rnd [ l [ k ]]   <   rnd [ k ])   rrotate ( k ); 
     } 
   } 
   bool   del ( int   & k ,   int   x )   {    // 删除节点 
     if   ( ! k )   return   false ; 
     if   ( val [ k ]   ==   x )   { 
       if   ( w [ k ]   >   1 )   { 
         w [ k ] -- ; 
         size_ [ k ] -- ; 
         return   true ; 
       } 
       if   ( l [ k ]   ==   0   ||   r [ k ]   ==   0 )   { 
         k   =   l [ k ]   +   r [ k ]; 
         return   true ; 
       }   else   if   ( rnd [ l [ k ]]   <   rnd [ r [ k ]])   { 
         rrotate ( k ); 
         return   del ( k ,   x ); 
       }   else   { 
         lrotate ( k ); 
         return   del ( k ,   x ); 
       } 
     }   else   if   ( val [ k ]   <   x )   { 
       bool   succ   =   del ( r [ k ],   x ); 
       if   ( succ )   size_ [ k ] -- ; 
       return   succ ; 
     }   else   { 
       bool   succ   =   del ( l [ k ],   x ); 
       if   ( succ )   size_ [ k ] -- ; 
       return   succ ; 
     } 
   } 
   int   queryrank ( int   k ,   int   x )   { 
     if   ( ! k )   return   0 ; 
     if   ( val [ k ]   ==   x ) 
       return   size_ [ l [ k ]]   +   1 ; 
     else   if   ( x   >   val [ k ])   { 
       return   size_ [ l [ k ]]   +   w [ k ]   +   queryrank ( r [ k ],   x ); 
     }   else 
       return   queryrank ( l [ k ],   x ); 
   } 
   int   querynum ( int   k ,   int   x )   { 
     if   ( ! k )   return   0 ; 
     if   ( x   <=   size_ [ l [ k ]]) 
       return   querynum ( l [ k ],   x ); 
     else   if   ( x   >   size_ [ l [ k ]]   +   w [ k ]) 
       return   querynum ( r [ k ],   x   -   size_ [ l [ k ]]   -   w [ k ]); 
     else 
       return   val [ k ]; 
   } 
   void   querypre ( int   k ,   int   x )   { 
     if   ( ! k )   return ; 
     if   ( val [ k ]   <   x ) 
       ans   =   k ,   querypre ( r [ k ],   x ); 
     else 
       querypre ( l [ k ],   x ); 
   } 
   void   querysub ( int   k ,   int   x )   { 
     if   ( ! k )   return ; 
     if   ( val [ k ]   >   x ) 
       ans   =   k ,   querysub ( l [ k ],   x ); 
     else 
       querysub ( r [ k ],   x ); 
   } 
}   T ; 
using   std :: cin ; 
using   std :: cout ; 
int   main ()   { 
   cin . tie ( nullptr ) -> sync_with_stdio ( false ); 
   srand ( 123 ); 
   cin   >>   n ; 
   int   opt ,   x ; 
   for   ( int   i   =   1 ;   i   <=   n ;   i ++ )   { 
     cin   >>   opt   >>   x ; 
     if   ( opt   ==   1 ) 
       T . insert ( T . rt ,   x ); 
     else   if   ( opt   ==   2 ) 
       T . del ( T . rt ,   x ); 
     else   if   ( opt   ==   3 )   { 
       cout   <<   T . queryrank ( T . rt ,   x )   <<   '\n' ; 
     }   else   if   ( opt   ==   4 )   { 
       cout   <<   T . querynum ( T . rt ,   x )   <<   '\n' ; 
     }   else   if   ( opt   ==   5 )   { 
       T . ans   =   0 ; 
       T . querypre ( T . rt ,   x ); 
       cout   <<   T . val [ T . ans ]   <<   '\n' ; 
     }   else   if   ( opt   ==   6 )   { 
       T . ans   =   0 ; 
       T . querysub ( T . rt ,   x ); 
       cout   <<   T . val [ T . ans ]   <<   '\n' ; 
     } 
   } 
   return   0 ; 
} 
无旋 treap 指针实现 完整代码  以下是前文讲解的代码的完整版本,是普通平衡树的模板代码。
   1 
  2 
  3 
  4 
  5 
  6 
  7 
  8 
  9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
 42 
 43 
 44 
 45 
 46 
 47 
 48 
 49 
 50 
 51 
 52 
 53 
 54 
 55 
 56 
 57 
 58 
 59 
 60 
 61 
 62 
 63 
 64 
 65 
 66 
 67 
 68 
 69 
 70 
 71 
 72 
 73 
 74 
 75 
 76 
 77 
 78 
 79 
 80 
 81 
 82 
 83 
 84 
 85 
 86 
 87 
 88 
 89 
 90 
 91 
 92 
 93 
 94 
 95 
 96 
 97 
 98 
 99 
100 
101 
102 
103 
104 
105 
106 
107 
108 
109 
110 
111 
112 
113 
114 
115 
116 
117 
118 
119 
120 
121 
122 
123 
124 
125 
126 
127 
128 
129 
130 
131 
132 
133 
134 
135 
136 
137 
138 
139 
140 
141 
142 
143 
144 
145 
146 
147 
148 
149 
150 
151 
152 
153 
154 
155 
156 
157 
158 
159 
160 
161 
162 
163 
164 
165 
166 
167 
168 
169 
170 
171 
172 
173 
174 
175 
176 
177 
178 
179 
180 
181 // author: (ttzytt)[ttzytt.com] 
#include   <cstdio> 
#include   <cstdlib> 
#include   <ctime> 
#include   <tuple> 
using   namespace   std ; 
struct   Node   { 
   Node   * ch [ 2 ]; 
   int   val ,   prio ; 
   int   cnt ; 
   int   siz ; 
   Node ( int   _val )   :   val ( _val ),   cnt ( 1 ),   siz ( 1 )   { 
     ch [ 0 ]   =   ch [ 1 ]   =   nullptr ; 
     prio   =   rand (); 
   } 
   Node ( Node   * _node )   { 
     val   =   _node -> val ,   prio   =   _node -> prio ,   cnt   =   _node -> cnt ,   siz   =   _node -> siz ; 
   } 
   void   upd_siz ()   { 
     siz   =   cnt ; 
     if   ( ch [ 0 ]   !=   nullptr )   siz   +=   ch [ 0 ] -> siz ; 
     if   ( ch [ 1 ]   !=   nullptr )   siz   +=   ch [ 1 ] -> siz ; 
   } 
}; 
struct   none_rot_treap   { 
#define _3 second.second 
#define _2 second.first 
   Node   * root ; 
   pair < Node   * ,   Node   *>   split ( Node   * cur ,   int   key )   { 
     if   ( cur   ==   nullptr )   return   { nullptr ,   nullptr }; 
     if   ( cur -> val   <=   key )   { 
       auto   temp   =   split ( cur -> ch [ 1 ],   key ); 
       cur -> ch [ 1 ]   =   temp . first ; 
       cur -> upd_siz (); 
       return   { cur ,   temp . second }; 
     }   else   { 
       auto   temp   =   split ( cur -> ch [ 0 ],   key ); 
       cur -> ch [ 0 ]   =   temp . second ; 
       cur -> upd_siz (); 
       return   { temp . first ,   cur }; 
     } 
   } 
   tuple < Node   * ,   Node   * ,   Node   *>   split_by_rk ( Node   * cur ,   int   rk )   { 
     if   ( cur   ==   nullptr )   return   { nullptr ,   nullptr ,   nullptr }; 
     int   ls_siz   =   cur -> ch [ 0 ]   ==   nullptr   ?   0   :   cur -> ch [ 0 ] -> siz ; 
     if   ( rk   <=   ls_siz )   { 
       Node   * l ,   * mid ,   * r ; 
       tie ( l ,   mid ,   r )   =   split_by_rk ( cur -> ch [ 0 ],   rk ); 
       cur -> ch [ 0 ]   =   r ; 
       cur -> upd_siz (); 
       return   { l ,   mid ,   cur }; 
     }   else   if   ( rk   <=   ls_siz   +   cur -> cnt )   { 
       Node   * lt   =   cur -> ch [ 0 ]; 
       Node   * rt   =   cur -> ch [ 1 ]; 
       cur -> ch [ 0 ]   =   cur -> ch [ 1 ]   =   nullptr ; 
       return   { lt ,   cur ,   rt }; 
     }   else   { 
       Node   * l ,   * mid ,   * r ; 
       tie ( l ,   mid ,   r )   =   split_by_rk ( cur -> ch [ 1 ],   rk   -   ls_siz   -   cur -> cnt ); 
       cur -> ch [ 1 ]   =   l ; 
       cur -> upd_siz (); 
       return   { cur ,   mid ,   r }; 
     } 
   } 
   Node   * merge ( Node   * u ,   Node   * v )   { 
     if   ( u   ==   nullptr   &&   v   ==   nullptr )   return   nullptr ; 
     if   ( u   !=   nullptr   &&   v   ==   nullptr )   return   u ; 
     if   ( v   !=   nullptr   &&   u   ==   nullptr )   return   v ; 
     if   ( u -> prio   <   v -> prio )   { 
       u -> ch [ 1 ]   =   merge ( u -> ch [ 1 ],   v ); 
       u -> upd_siz (); 
       return   u ; 
     }   else   { 
       v -> ch [ 0 ]   =   merge ( u ,   v -> ch [ 0 ]); 
       v -> upd_siz (); 
       return   v ; 
     } 
   } 
   void   insert ( int   val )   { 
     auto   temp   =   split ( root ,   val ); 
     auto   l_tr   =   split ( temp . first ,   val   -   1 ); 
     Node   * new_node ; 
     if   ( l_tr . second   ==   nullptr )   { 
       new_node   =   new   Node ( val ); 
     }   else   { 
       l_tr . second -> cnt ++ ; 
       l_tr . second -> upd_siz (); 
     } 
     Node   * l_tr_combined   = 
         merge ( l_tr . first ,   l_tr . second   ==   nullptr   ?   new_node   :   l_tr . second ); 
     root   =   merge ( l_tr_combined ,   temp . second ); 
   } 
   void   del ( int   val )   { 
     auto   temp   =   split ( root ,   val ); 
     auto   l_tr   =   split ( temp . first ,   val   -   1 ); 
     if   ( l_tr . second -> cnt   >   1 )   { 
       l_tr . second -> cnt -- ; 
       l_tr . second -> upd_siz (); 
       l_tr . first   =   merge ( l_tr . first ,   l_tr . second ); 
     }   else   { 
       if   ( temp . first   ==   l_tr . second )   { 
         temp . first   =   nullptr ; 
       } 
       delete   l_tr . second ; 
       l_tr . second   =   nullptr ; 
     } 
     root   =   merge ( l_tr . first ,   temp . second ); 
   } 
   int   qrank_by_val ( Node   * cur ,   int   val )   { 
     auto   temp   =   split ( cur ,   val   -   1 ); 
     int   ret   =   ( temp . first   ==   nullptr   ?   0   :   temp . first -> siz )   +   1 ; 
     root   =   merge ( temp . first ,   temp . second ); 
     return   ret ; 
   } 
   int   qval_by_rank ( Node   * cur ,   int   rk )   { 
     Node   * l ,   * mid ,   * r ; 
     tie ( l ,   mid ,   r )   =   split_by_rk ( cur ,   rk ); 
     int   ret   =   mid -> val ; 
     root   =   merge ( merge ( l ,   mid ),   r ); 
     return   ret ; 
   } 
   int   qprev ( int   val )   { 
     auto   temp   =   split ( root ,   val   -   1 ); 
     int   ret   =   qval_by_rank ( temp . first ,   temp . first -> siz ); 
     root   =   merge ( temp . first ,   temp . second ); 
     return   ret ; 
   } 
   int   qnex ( int   val )   { 
     auto   temp   =   split ( root ,   val ); 
     int   ret   =   qval_by_rank ( temp . second ,   1 ); 
     root   =   merge ( temp . first ,   temp . second ); 
     return   ret ; 
   } 
}; 
none_rot_treap   tr ; 
int   main ()   { 
   srand ( time ( nullptr )); 
   int   t ; 
   scanf ( "%d" ,   & t ); 
   while   ( t -- )   { 
     int   mode ; 
     int   num ; 
     scanf ( "%d%d" ,   & mode ,   & num ); 
     switch   ( mode )   { 
       case   1 : 
         tr . insert ( num ); 
         break ; 
       case   2 : 
         tr . del ( num ); 
         break ; 
       case   3 : 
         printf ( "%d \n " ,   tr . qrank_by_val ( tr . root ,   num )); 
         break ; 
       case   4 : 
         printf ( "%d \n " ,   tr . qval_by_rank ( tr . root ,   num )); 
         break ; 
       case   5 : 
         printf ( "%d \n " ,   tr . qprev ( num )); 
         break ; 
       case   6 : 
         printf ( "%d \n " ,   tr . qnex ( num )); 
         break ; 
     } 
   } 
} 
无旋 treap 的区间操作 指针实现 完整代码  以下是前文讲解的代码的完整版本,是文艺平衡树题目的模板代码。
   1 
  2 
  3 
  4 
  5 
  6 
  7 
  8 
  9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
 42 
 43 
 44 
 45 
 46 
 47 
 48 
 49 
 50 
 51 
 52 
 53 
 54 
 55 
 56 
 57 
 58 
 59 
 60 
 61 
 62 
 63 
 64 
 65 
 66 
 67 
 68 
 69 
 70 
 71 
 72 
 73 
 74 
 75 
 76 
 77 
 78 
 79 
 80 
 81 
 82 
 83 
 84 
 85 
 86 
 87 
 88 
 89 
 90 
 91 
 92 
 93 
 94 
 95 
 96 
 97 
 98 
 99 
100 
101 
102 
103 
104 
105 
106 
107 
108 
109 
110 
111 
112 
113 
114 
115 
116 
117 
118 
119 
120 
121 
122 
123 
124 // author: (ttzytt)[ttzytt.com] 
#include   <cstdlib> 
#include   <ctime> 
#include   <iostream> 
using   namespace   std ; 
// 参考:https://www.cnblogs.com/Equinox-Flower/p/10785292.html 
struct   Node   { 
   Node *   ch [ 2 ]; 
   int   val ,   prio ; 
   int   cnt ; 
   int   siz ; 
   bool   to_rev   =   false ;    // 需要把这个子树下的每一个节点都翻转过来 
   Node ( int   _val )   :   val ( _val ),   cnt ( 1 ),   siz ( 1 )   { 
     ch [ 0 ]   =   ch [ 1 ]   =   nullptr ; 
     prio   =   rand (); 
   } 
   int   upd_siz ()   { 
     siz   =   cnt ; 
     if   ( ch [ 0 ]   !=   nullptr )   siz   +=   ch [ 0 ] -> siz ; 
     if   ( ch [ 1 ]   !=   nullptr )   siz   +=   ch [ 1 ] -> siz ; 
     return   siz ; 
   } 
   void   pushdown ()   { 
     swap ( ch [ 0 ],   ch [ 1 ]); 
     if   ( ch [ 0 ]   !=   nullptr )   ch [ 0 ] -> to_rev   ^=   1 ; 
     // 如果原来子节点也要翻转,那两次翻转就抵消了,如果子节点不翻转,那这个 
     //  tag 就需要继续被 push 到子节点上 
     if   ( ch [ 1 ]   !=   nullptr )   ch [ 1 ] -> to_rev   ^=   1 ; 
     to_rev   =   false ; 
   } 
   void   check_tag ()   { 
     if   ( to_rev )   pushdown (); 
   } 
}; 
struct   Seg_treap   { 
   Node *   root ; 
#define siz(_) (_ == nullptr ? 0 : _->siz) 
   pair < Node * ,   Node *>   split ( Node *   cur ,   int   sz )   { 
     // 按照树的大小划分 
     if   ( cur   ==   nullptr )   return   { nullptr ,   nullptr }; 
     cur -> check_tag (); 
     if   ( sz   <=   siz ( cur -> ch [ 0 ]))   { 
       // 左边的子树就够了 
       auto   temp   =   split ( cur -> ch [ 0 ],   sz ); 
       // 左边的子树不一定全部需要,temp.second 是不需要的 
       cur -> ch [ 0 ]   =   temp . second ; 
       cur -> upd_siz (); 
       return   { temp . first ,   cur }; 
     }   else   { 
       // 左边的加上右边的一部分(当然也包括这个节点本身) 
       auto   temp   =   split ( cur -> ch [ 1 ],   sz   -   siz ( cur -> ch [ 0 ])   -   1 ); 
       cur -> ch [ 1 ]   =   temp . first ; 
       cur -> upd_siz (); 
       return   { cur ,   temp . second }; 
     } 
   } 
   Node *   merge ( Node *   sm ,   Node *   bg )   { 
     // small, big 
     if   ( sm   ==   nullptr   &&   bg   ==   nullptr )   return   nullptr ; 
     if   ( sm   !=   nullptr   &&   bg   ==   nullptr )   return   sm ; 
     if   ( sm   ==   nullptr   &&   bg   !=   nullptr )   return   bg ; 
     sm -> check_tag (),   bg -> check_tag (); 
     if   ( sm -> prio   <   bg -> prio )   { 
       sm -> ch [ 1 ]   =   merge ( sm -> ch [ 1 ],   bg ); 
       sm -> upd_siz (); 
       return   sm ; 
     }   else   { 
       bg -> ch [ 0 ]   =   merge ( sm ,   bg -> ch [ 0 ]); 
       bg -> upd_siz (); 
       return   bg ; 
     } 
   } 
   void   insert ( int   val )   { 
     auto   temp   =   split ( root ,   val ); 
     auto   l_tr   =   split ( temp . first ,   val   -   1 ); 
     Node *   new_node ; 
     if   ( l_tr . second   ==   nullptr )   new_node   =   new   Node ( val ); 
     Node *   l_tr_combined   = 
         merge ( l_tr . first ,   l_tr . second   ==   nullptr   ?   new_node   :   l_tr . second ); 
     root   =   merge ( l_tr_combined ,   temp . second ); 
   } 
   void   seg_rev ( int   l ,   int   r )   { 
     // 这里的 less 和 more 是相对于 l 的 
     auto   less   =   split ( root ,   l   -   1 ); 
     // 所有小于等于 l - 1 的会在 less 的左边 
     auto   more   =   split ( less . second ,   r   -   l   +   1 ); 
     // 拿出从 l 开始的前 r - l + 1 个 
     more . first -> to_rev   =   true ; 
     root   =   merge ( less . first ,   merge ( more . first ,   more . second )); 
   } 
   void   print ( Node *   cur )   { 
     if   ( cur   ==   nullptr )   return ; 
     cur -> check_tag (); 
     print ( cur -> ch [ 0 ]); 
     cout   <<   cur -> val   <<   " " ; 
     print ( cur -> ch [ 1 ]); 
   } 
}; 
Seg_treap   tr ; 
int   main ()   { 
   srand ( time ( nullptr )); 
   int   n ,   m ; 
   cin   >>   n   >>   m ; 
   for   ( int   i   =   1 ;   i   <=   n ;   i ++ )   tr . insert ( i ); 
   while   ( m -- )   { 
     int   l ,   r ; 
     cin   >>   l   >>   r ; 
     tr . seg_rev ( l ,   r ); 
   } 
   tr . print ( tr . root ); 
} 
例题 普通平衡树 
文艺平衡树(Splay) 
「ZJOI2006」书架 
「NOI2005」维护数列 
CF 702F T-Shirts 
参考资料与注释 2025/10/13 16:54:57 ,更新历史 在 GitHub 上编辑此页! Ir1d , Tiphereth-A , ttzytt , Dev-XYS , Enter-tainer , hsfzLZH1 , Menci , mgt , ouuan , qwqAutomaton , shuzhouliu , Sora233 , untitledunrevised , c-forrest , CCXXXI , cesonic , ChungZH , Early0v0 , Henry-ZHR , HeRaNO , HRiver2 , isdanni , kenlig , ksyx , llleixx , lychees , Q-wjh213 , Qiyuan Gu , scp020 , StudyingFather , sun2snow , TrisolarisHD , tsawke , zcz0263 , zhcpku CC BY-SA 4.0  和 SATA