非旋Treap

最近看到有一种不用旋转的treap,好像还可以持久化,于是就学了一下。

如果你还不会Treap的话,那你可以点击这里,对旋转Treap有个大致了解,这里就不赘述Treap的性质了。

treap就是tree+heap。它的每个节点的权值data满足排序二叉树的性质,随机权值key满足堆的性质。由于key是随机的所以它大致是平衡的。
不基于旋转的treap有两个基本操作:

merge(a,b):返回一个treap,包含a,b两个treap中的所有节点,但要保证b中所有节点权值都大于等于a。

split(a,n)返回两个treap l,r。其中l中包含treap a中的前n个节点,r中包含treap a中的剩余节点。

这两个操作的实现都很简单(这里我们维护小根堆的性质):

merge(a,b):若a的key< b的key则将a的右儿子变为merge(a的右儿子,b)。
否则将b的左儿子变为merge(a,b的左儿子)。

split(a,n):若a左子树的size(记为m)=n则返回a的左子树,a和a的右子树。若m=n-1则返回a的左子树和a,a的右子树。否则若m>n则设{l,r}为split(a的左子树,n)将a的左子树设为r,返回l,a。若m< n-1则设{l,r}为split(a的右子树,n-m-1)将a的右儿子设为l,返回a,r。

有了这两操作我们就可以实现插入和删除了。

插入x:找到x所在位置,将其split开,在合并l,x与x,r。

删除x:找到x的位置,将x与其前后位置都split开,在合并另外两部份。
其余操作和不同平衡树一样。



【代码实现】

  1 #include<cstdio>
  2 #include<algorithm>
  3 #define maxn 100010
  4 #define mp make_pair
  5 using namespace std;
  6 typedef pair<int,int> pp;
  7 struct node{
  8     int son[2],val,rd,size;
  9 }t[maxn];
 10 int op,n,root,cnt;
 11 const int INF=1e9+7;
 12 void updata(int v){t[v].size=t[t[v].son[0]].size+t[t[v].son[1]].size+1;}
 13 pp split(int v,int k)
 14 {
 15     if(k==0)return mp(0,v);
 16     int ls=t[v].son[0],rs=t[v].son[1];
 17     if(t[ls].size==k) {t[v].son[0]=0,updata(v);return mp(ls,v);}
 18     if(t[ls].size+1==k) {t[v].son[1]=0,updata(v);return mp(v,rs);}
 19     if(t[ls].size>k)
 20     {
 21         pp tmp=split(ls,k);
 22         t[v].son[0]=tmp.second,updata(v);
 23         return mp(tmp.first,v);
 24     }
 25     pp tmp=split(rs,k-t[ls].size-1);
 26     t[v].son[1]=tmp.first,updata(v);
 27     return mp(v,tmp.second);
 28 }
 29 int merge(int x,int y)
 30 {
 31     if(!x||!y)return x+y;
 32     if(t[x].rd<t[y].rd) {t[x].son[1]=merge(t[x].son[1],y),updata(x);return x;}
 33     else {t[y].son[0]=merge(x,t[y].son[0]),updata(y);return y;}
 34 }
 35 int rank(int v,int k)
 36 {
 37     int ans=0,tmp=INF;
 38     while(v)
 39     {
 40         if(k==t[v].val)tmp=min(tmp,ans+t[t[v].son[0]].size+1);
 41         if(k>t[v].val)ans+=t[t[v].son[0]].size+1,v=t[v].son[1];
 42         else v=t[v].son[0];
 43     }
 44     return tmp==INF?ans:tmp;
 45 }
 46 int find(int v,int k)
 47 {
 48     while(1)
 49     {
 50         if(t[t[v].son[0]].size+1==k)return t[v].val;
 51         if(t[t[v].son[0]].size>=k)v=t[v].son[0];
 52         else k=k-t[t[v].son[0]].size-1,v=t[v].son[1];
 53     }
 54 }
 55 int pre(int v,int k)
 56 {
 57     int ans=-INF;
 58     while(v)
 59     {
 60         if(t[v].val<k)ans=max(ans,t[v].val),v=t[v].son[1];
 61         else v=t[v].son[0];
 62     }
 63     return ans;
 64 }
 65 int next(int v,int k)
 66 {
 67     int ans=INF;
 68     while(v)
 69     {
 70         if(t[v].val>k)ans=min(ans,t[v].val),v=t[v].son[0];
 71         else v=t[v].son[1];
 72     }
 73     return ans;
 74 }
 75 void insert(int v)
 76 {
 77     int k=rank(root,v);
 78     pp tmp=split(root,k);
 79     t[++cnt].val=v;
 80     t[cnt].rd=rand();
 81     t[cnt].size=1;
 82     root=merge(tmp.first,cnt);
 83     root=merge(root,tmp.second);
 84 }
 85 void del(int v)
 86 {
 87     int k=rank(root,v);
 88     pp tmp1=split(root,k);
 89     pp tmp2=split(tmp1.first,k-1);
 90     root=merge(tmp2.first,tmp1.second);
 91 }
 92 int main()
 93 {
 94     int x;
 95     scanf("%d",&n);
 96     for(int i=1;i<=n;i++)
 97     {
 98         scanf("%d%d",&op,&x);
 99         if(op==1)insert(x);
100         else if(op==2)del(x);
101         else if(op==3)printf("%d\n",rank(root,x));
102         else if(op==4)printf("%d\n",find(root,x));
103         else if(op==5)printf("%d\n",pre(root,x));
104         else printf("%d\n",next(root,x));
105     }
106     return 0;
107 }

原文地址:https://www.cnblogs.com/genius777/p/9368896.html

时间: 2024-08-02 02:56:01

非旋Treap的相关文章

沉迷数据结构1(treap&amp;非旋treap)

Achen大佬说不要沉迷数据结构否则智商会降低的. 从省选考完后就开始学treap,首先是自己yy了一个打了两百多行,然后debug了2个月还是3个月记不清了. 最后弃疗,去找了网上别人的代码抄了一遍. noip考完后补常规的一段时间,羡慕Achen能20分钟打出一个treap模板,于是自己也开始走上打板子的不归路. 到了后来可以10分钟左右打出一个结构体版的treap,看了Achen的数组版treap,觉得自己结构体版的太不优秀啦,于是就换成数组版的. 然后现在有几周没有碰过treap,感觉又

2827: 千山鸟飞绝 非旋treap

国际惯例的题面:看起来很不可做的样子,我们先来整理一下题意吧.就是,维护每个点曾经拥有过的最大的两个属性值,支持把点的位置移动.我们用map对每个位置进行离散化,对每个位置建立一个平衡树.为了方便分离,这个平衡树的关键字是节点编号.然后把每个点当做一个节点,放进其所在位置的平衡树里.剩下要做的就是平衡树分离出一个点,合并一个点,还有打标记了.对于士气值的标记,我们维护平衡树中的max,每次合并的时候,用这个新点的威武值去给整棵树打标记,再用树中的max给这个新点打标记.团结值的标记,合并后一起打

非旋 treap 结构体数组版(无指针)详解,有图有真相

非旋  $treap$ (FHQ treap)的简单入门 前置技能 建议在掌握普通 treap 以及 左偏堆(也就是可并堆)食用本blog 原理 以随机数维护平衡,使树高期望为logn级别, FHQ 不依靠旋转,只有两个核心操作merge(合并)和split(拆分) 所谓随机数维护平衡就是给每个节点一个随机值 key (下文中没有加随机的就代表是真实权值), 然后整棵树中 key 值要满足小(大)根堆的性质(也就是heap), 同时也要满足平衡树(tree)的性质(也就是每个节点左子树内节点真实

非旋Treap总结 : 快过Splay 好用过传统Treap

非旋$Treap$ 其高级名字叫$Fhq\ Treap$,既然叫$Treap$,它一定满足了$Treap$的性质(虽然可能来看这篇的人一定知道$Treap$,但我还是多说几句:$Fhp\ Treap$就是继承了$Treap$的随机系统,在二叉搜索的基础上,每个点加一个随机化处理,这些随机值满足堆的性质……通俗一点讲,就是$Fhp\ Treap$它每个点有至少两个值,一个是val,即存的数值,这些数值满足二叉搜索树,也就是父亲比左孩子小/大,则右孩子比父亲小/大:还有一个是key,是个随机值,这些

[BZOJ 3224] 普通平衡树 非旋Treap

题意 维护一个多重集合 $S$ , 支持: ① 插入一个数 $w$ . ② 删除一个数 $w$ . ③ 查询 $w$ 在集合中的排名. ④ 查询集合中排名第 $r$ 的数. ⑤ 求集合中 $w$ 的前驱. ⑥ 求集合中 $w$ 的后继. $N \le 100000$ . 小结 要总结一些常见的写法和想法, 减小实现时的复杂度. 实现 #include <cstdio> #include <cstring> #include <cstdlib> #include <

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

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

模板 - 数据结构 - 无旋Treap / FHQ Treap

普通平衡树: #include<bits/stdc++.h> using namespace std; typedef long long ll; #define ls(p) ch[p][0] #define rs(p) ch[p][1] const int MAXN = 100000 + 5; int val[MAXN], ch[MAXN][2], rnd[MAXN], siz[MAXN], tot, root; void Init() { tot = root = 0; } void Pu

非旋转Treap

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

种下一棵树:有旋Treap

第一个平衡树板子,有旋Treap.用随机函数规定一个堆,维护点权的同时维护堆的性质,可以有效地避免退化成链.按我的理解,建立一棵二叉排序树,树的形态会和给出节点的顺序有关.按照出题人很机智定理,数据肯定不会太容易操作,这时候就需要我们自行调整"数据顺序",平衡树应运而生. 这个板子涵盖的操作有左旋.右旋(维护堆性质).添加节点.删除节点(建树相关).查找第x个元素.查找元素排名.查找前驱.查找后继这8种操作.改变树本身的操作都是取地址传参的,询问操作则都是简单传参.原理不是太难理解,应