普通平衡树——非旋转treap

题目:

此为平衡树系列第一道:普通平衡树您需要写一种数据结构,来维护一些数,其中需要提供以下操作:
1. 插入x数
2. 删除x数(若有多个相同的数,因只删除一个)
3. 查询x数的排名(若有多个相同的数,因输出最小的排名)
4. 查询排名为x的数
5. 求x的前驱(前驱定义为小于x,且最大的数)
6. 求x的后继(后继定义为大于x,且最小的数)

n<=100000 所有数字均在-107到107内。

输入样例:

10
1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598

输出样例:

106465
84185
492737

变量声明:size[x],以x为根节点的子树大小;ls[x],x的左儿子;rs[x],x的右子树;r[x],x节点的随机数;v[x],x节点的权值。

root,树的总根;tot,树的大小。

非旋转treap不同于旋转treap需要靠旋转来维护平衡树的性质,他的操作可以用简单暴力来形容——只有合并和断裂两个操作。他不但有treap的优良性质,还有许多优点:支持可持久化和区间操作,常数比splay小。

下面介绍一下非旋转treap的这两个操作:

1.断裂

就是去掉一条边,把treap拆分成两棵树,对于区间操作可以进行两次断裂来分割出一段区间再进行操作。

以查找value为例,从root往下走,如果v[x]>value,那么下一步走ls[x],之后的点都比x小,把x接到右树上,下一次再接到右树上的点就是x的左儿子。

v[x]<=value与上述类似,在这里不加赘述。

 1 void split(int x,int &lroot,int &rroot,int val)
 2 {
 3     if(!x)
 4     {
 5         lroot=rroot=0;
 6         return ;
 7     }
 8     if(v[x]<=val)
 9     {
10         lroot=x;
11         split(rs[x],rs[lroot],rroot,val);
12     }
13     else
14     {
15         rroot=x;
16         split(ls[x],lroot,ls[rroot],val);
17     }
18     up(x);
19 }

2.合并

就是把断裂开的树合并起来,因为要维护堆的性质所以按可并堆来合并。

 1 void merge(int &x,int a,int b)
 2 {
 3     if(!a||!b)
 4     {
 5         x=a+b;
 6         return ;
 7     }
 8     if(r[a]<r[b])
 9     {
10         x=a;
11         merge(rs[x],rs[a],b);
12     }
13     else
14     {
15         x=b;
16         merge(ls[x],a,ls[b]);
17     }
18     up(x);
19 }

为了方便删除,所以建议把相同权值的点分开来加入树中,不要都放在同一个点。

非旋转treap代码比较短(为了清晰我写的比较长qwq),但唯一的缺点就是难理解。

  1 #include<cstdio>
  2 #include<cstring>
  3 #include<algorithm>
  4 #include<cmath>
  5 #include<iostream>
  6 using namespace std;
  7 int INF=1000000000;
  8 int n;
  9 int opt,x;
 10 int r[100010];
 11 int ls[100010];
 12 int rs[100010];
 13 int size[100010];
 14 int v[100010];
 15 int root;
 16 int tot;
 17 void up(int x)
 18 {
 19     size[x]=size[ls[x]]+size[rs[x]]+1;
 20 }
 21 void build(int &x,int val)
 22 {
 23     tot++;
 24     size[tot]=1;
 25     r[tot]=rand();
 26     v[tot]=val;
 27     ls[tot]=rs[tot]=0;
 28     x=tot;
 29 }
 30 void merge(int &x,int a,int b)
 31 {
 32     if(!a||!b)
 33     {
 34         x=a+b;
 35         return ;
 36     }
 37     if(r[a]<r[b])
 38     {
 39         x=a;
 40         merge(rs[x],rs[a],b);
 41     }
 42     else
 43     {
 44         x=b;
 45         merge(ls[x],a,ls[b]);
 46     }
 47     up(x);
 48 }
 49 void split(int x,int &lroot,int &rroot,int val)
 50 {
 51     if(!x)
 52     {
 53         lroot=rroot=0;
 54         return ;
 55     }
 56     if(v[x]<=val)
 57     {
 58         lroot=x;
 59         split(rs[x],rs[lroot],rroot,val);
 60     }
 61     else
 62     {
 63         rroot=x;
 64         split(ls[x],lroot,ls[rroot],val);
 65     }
 66     up(x);
 67 }
 68 void insert_sum(int val)
 69 {
 70     int x=0;
 71     int y=0;
 72     int z=0;
 73     build(z,val);
 74     split(root,x,y,val);
 75     merge(x,x,z);
 76     merge(root,x,y);
 77 }
 78 void delete_sum(int val)
 79 {
 80     int x=0;
 81     int y=0;
 82     int z=0;
 83     split(root,x,y,val);
 84     split(x,x,z,val-1);
 85     merge(z,ls[z],rs[z]);
 86     merge(x,x,z);
 87     merge(root,x,y);
 88 }
 89 void ask_rank(int val)
 90 {
 91     int x=0;
 92     int y=0;
 93     split(root,x,y,val-1);
 94     printf("%d\n",size[x]+1);
 95     merge(root,x,y);
 96 }
 97 void ask_sum(int x,int num)
 98 {
 99     while(size[ls[x]]+1!=num)
100     {
101         if(num<=size[ls[x]])
102         {
103             x=ls[x];
104         }
105         else
106         {
107             num-=(size[ls[x]]+1);
108             x=rs[x];
109         }
110     }
111     printf("%d\n",v[x]);
112 }
113 void ask_front(int val)
114 {
115     int x=0;
116     int y=0;
117     split(root,x,y,val-1);
118     ask_sum(x,size[x]);
119     merge(root,x,y);
120 }
121 void ask_back(int val)
122 {
123     int x=0;
124     int y=0;
125     split(root,x,y,val);
126     ask_sum(y,1);
127     merge(root,x,y);
128 }
129 int main()
130 {
131     srand(16);
132     scanf("%d",&n);
133     for(int i=1;i<=n;i++)
134     {
135         scanf("%d%d",&opt,&x);
136         if(opt==1)
137         {
138             insert_sum(x);
139         }
140         else if(opt==2)
141         {
142             delete_sum(x);
143         }
144         else if(opt==3)
145         {
146             ask_rank(x);
147         }
148         else if(opt==4)
149         {
150             ask_sum(root,x);
151         }
152         else if(opt==5)
153         {
154             ask_front(x);
155         }
156         else if(opt==6)
157         {
158             ask_back(x);
159         }
160     }
161     return 0;
162 }

原文地址:https://www.cnblogs.com/Khada-Jhin/p/8992409.html

时间: 2024-08-02 02:55:58

普通平衡树——非旋转treap的相关文章

4923: [Lydsy1706月赛]K小值查询 平衡树 非旋转Treap

国际惯例的题面:这种维护排序序列,严格大于的进行操作的题都很套路......我们按照[0,k],(k,2k],(2k,inf)分类讨论一下就好.显然第一个区间的不会变化,第二个区间的会被平移进第一个区间,第三个区间的相对大小不会变化.于是我们直接把第二个区间拆了重构,一个一个插入第一个区间即可.因为每次这样做最少减半,所以每个元素只会被重构log次,复杂度nlog^2n.这种按照值域分离区间的操作,非旋转treap实现起来是最简单的......然而第一次写非旋转treap还是出了一点问题,注意它

平衡树讲解(旋转treap,非旋转treap,splay)

在刷了许多道平衡树的题之后,对平衡树有了较为深入的理解,在这里和大家分享一下,希望对大家学习平衡树能有帮助. 平衡树有好多种,比如treap,splay,红黑树,STL中的set.在这里只介绍几种常用的:treap和splay(其中treap包括旋转treap和非旋转treap). 一.treap treap这个词是由tree和heap组合而成,意思是树上的的堆(其实就是字面意思啦qwq).treap可以说是由二叉搜索树(BST)进化而来,二叉搜索树每个点满足它左子树中所有点权值都比它小,它右子

[bzoj1895][Pku3580]supermemo_非旋转Treap

supermemo bzoj-1895 Pku-3580 题目大意:给定一个n个数的序列,需支持:区间加,区间翻转,区间平移,单点插入,单点删除,查询区间最小值. 注释:$1\le n\le 6.1\cdot 10^6$. 想法: 这数据范围给的我真是醉了. 显然用平衡树,这里用非旋转Treap,题目让你干什么你就干什么. 区间加:撕出子树区间后打标记维护区间加. 区间翻转:撕出子树区间后打标记维护区间翻转. 区间平移:相当于两段相邻区间交换,所以撕成四部分:左边,第一个区间,第二个区间,右边.

非旋转Treap详解

利用其他人其中考试的时间,终于学完了非旋转Treap,它与普通Treap的区别就是它不旋转废话.前置知识只有BST和可并堆. BST看这个博客,解释的挺清楚的.https://www.cnblogs.com/jiangminghong/p/9999884.html 可并堆就是用很快的时间合并两个堆.如果裸上一个并查集的话就是nlog2n.这个复杂度我们是不能接受的.正常的可并堆是维护一棵左偏树,我们用一个参数dis[x]表示从x点出发能够向右走的最大步数.每次两个堆合并时,我们就把一个堆扔到另一

非旋转Treap

Treap是一种平衡二叉树,同时也是一个堆.它既具有二叉查找树的性质,也具有堆的性质.在对数据的查找.插入.删除.求第k大等操作上具有期望O(log2n)的复杂度.     Treap可以通过节点的旋转来实现其维持平衡的操作,详见旋转式Treap. 而旋转式Treap在对区间数据的操作上无能为力,这就需要非旋转式Treap来解决这些区间问题. 非旋转式Treap支持的操作 基本操作: 操作 说明 实现复杂度 Build 构造Treap O(n) Merge 合并Treap O(log2n) Sp

[bzoj3173]最长上升子序列_非旋转Treap

最长上升子序列 bzoj-3173 题目大意:有1-n,n个数,第i次操作是将i加入到原有序列中制定的位置,后查询当前序列中最长上升子序列长度. 注释:1<=n<=10,000,开始序列为空. 想法:显然,我们发现,我每次加入的数一定是当前序列中最大的,所以,刚刚加入的i,要么是当前序列中LIS的结尾,要么不属于LIS.根据这个性质,我们想到:在Treap中维护这样的性质,就是维护每个数加入节点的编号.然后,我们更新新节点的方式就是它的左子树和右子树的LIS取最大+1.其实最重要的就是如何加入

luoguP5055 【模板】可持久化文艺平衡树 可持久化非旋转treap

好题. Code: #include<bits/stdc++.h> using namespace std; #define setIO(s) freopen(s".in","r",stdin) namespace fhqtreap{ #define maxn 20000000 #define ll long long int tot,m,tr; int trash[maxn]; int ls[maxn],rs[maxn],rev[maxn],val[m

斜堆,非旋转treap,替罪羊树

一.斜堆 斜堆是一种可以合并的堆 节点信息: struct Node { int v; Node *ch[2]; }; 主要利用merge函数 Node *merge(Node *x, Node *y) { if(!x) return y; if(!y) return x; if(x->v < y->v) swap(x, y); x->ch[1] = merge(x->ch[1], y); return swap(x->ch[0], x->ch[1]), x; }

【模板】非旋转Treap

Treap,也叫做树堆,是指有一个随机附加域满足堆的性质的二叉搜索树. 如果一棵二叉搜索树插入节点的顺序是随机的,那我们得到的二叉搜索树在大多数情况下是平衡的,期望高度是log(n). 但有些情况下我们并不能得知所有待插入节点,打乱以后再插入,这时我们需要给二叉搜索树加上一个随机附加域,并使这个随机附加域(优先级)满足堆的性质,以此来实现"乱序插入"的想法,使二叉搜索树保持平衡. Treap可以满足的序列操作: 1,插入一个数x 2,删除一个数x 3,查询x的排名 4,查询排名为x的数