[您有新的未分配科技点]无旋treap:从好奇到入门(例题:bzoj3224 普通平衡树)

今天我们来学习一种新的数据结构:无旋treap。它和splay一样支持区间操作,和treap一样简单易懂,同时还支持可持久化。

无旋treap的节点定义和treap一样,都要同时满足树性质和堆性质,我们还是用rand()来实现平衡

而无旋treap与treap不同的地方,也是其核心,就是它不旋转用两个新的核心函数:merge函数(合并两棵子树)和split函数(分裂出某棵树的前k个节点,并且作为一棵树返回)

首先看merge函数,它是一个递归实现的过程,先看代码:

 1 Treap *merge(Treap *a,Treap *b)
 2 {
 3     if(a==null)return b;
 4     if(b==null)return a;
 5     pushdown(a);pushdown(b);
 6     if(a->key < b->key)
 7         {a->ch[1]=merge(a->ch[1],b);a->update();return a;}
 8     else
 9         {b->ch[0]=merge(a,b->ch[0]);b->update();return b;}
10 }

对于两棵子树a和b,我们可以实现把b树合并到a树中

在合并时,我们首先看他们的根节点谁的键值比较小(我维护的是一个小根堆),并且建立对应的父子关系。

又由于平衡树的中序遍历不变,我们又要把b插在a后面,维持一个确定的中序遍历,

所以我们应该一直把a作为merge函数的前一个参数,b作为后一个参数,这个顺序不能换.

这一个确定的顺序的重要性尤其体现在后续的区间操作中。刚开始的时候可以当板子背下来,但随着打题肯定会逐渐理解。

接下来我们介绍split函数,这也是一个递归实现的过程,还是先看代码:

 1 typedef pair<Treap*,Treap*> D;
 2 D split(Treap *o,int k)
 3 {
 4     if(o==null) return D(null,null);
 5     D y;pushdown(o);
 6     if(o->ch[0]->size>=k)
 7         {y=split(o->ch[0],k);o->ch[0]=y.second;o->update();y.second=o;}
 8     else
 9         {y=split(o->ch[1],k-o->ch[0]->size-1);o->ch[1]=y.first;o->update();y.first=o;}
10     return y;
11 }

我们首先定义一个pair,这样做的好处是同时返回分裂出来的两棵树的根节点指针,我规定第一个是分离完成的树,第二个是剩下的原树。

然后考虑分离前k个的过程:如果o的左儿子有k个以上节点,我们显然应该去左儿子分离。

然后我们会得到分离完成的树和左儿子剩下的树,这时候把左儿子剩下的部分接回节点o,并把新的o作为分离o剩下的原树

如果左儿子节点个数不够,我们就去右儿子分离,过程是相似的,但略有不同,留给读者思考。

有了这两个函数,我们就可以用他们实现一些常用的操作了,比如:

insert=split+newnode+merge+merge

delete=split+split+merge(合并第一个split的first和第二个的second)

等等,其他操作也可以用类似的思路打出来。下面我们用一道例题实战一下。建议读者自己实现代码并充分思考后再核对标程。

3224: Tyvj 1728 普通平衡树

Time Limit: 10 Sec  Memory Limit: 128 MB

Description

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

Input

第一行为n,表示操作的个数,下面n行每行有两个数opt和x,opt表示操作的序号(1<=opt<=6)

Output

对于操作3,4,5,6每行输出一个数,表示对应答案

Sample Input

10

1 106465

4 1

1 317721

1 460929

1 644985

1 84185

1 89851

6 81968

1 492737

5 493598

Sample Output

106465

84185

492737

HINT

1.n的数据范围:n<=100000

2.每个数的数据范围:[-2e9,2e9]

题解:

这道题本质上只比上面讲的基本操作多了两个函数:查询某个权值的排名和查询某个排名的权值

查询某个权值的排名很简单,在树中递归询问即可

而对于某个权值的排名,我们可以考虑split前k-1个节点,再对第一次split的second进行split,得到第k个节点,并且返回权值

前驱和后继只是上面这两个操作的简单变形,但稍微需要注意一下边界的处理。

代码见下:

 1 #include <cstdio>
 2 #include <algorithm>
 3 #include <cstring>
 4 #include <ctime>
 5 #include <cstdlib>
 6 using namespace std;
 7 const int maxn=100100,inf=0x7fffffff;
 8 struct Treap
 9 {
10     Treap* ch[2];
11     int key,val,size;
12     Treap(int v)
13         {size=1,val=v,key=rand();ch[0]=ch[1]=NULL;}
14     inline void tain()
15         {size=1+(ch[0]?ch[0]->size:0)+(ch[1]?ch[1]->size:0);}
16 }*root;
17 typedef pair<Treap*,Treap*> D;
18 inline int size(Treap *o){return o?o->size:0;}
19 Treap *Merge(Treap *a,Treap* b)
20 {
21     if(!a)return b;
22     if(!b)return a;
23     if(a->key < b->key)
24         {a->ch[1]=Merge(a->ch[1],b);a->tain();return a;}
25     else
26         {b->ch[0]=Merge(a,b->ch[0]);b->tain();return b;}
27 }
28 D Split(Treap *o,int k)
29 {
30     if(!o)return D(NULL,NULL);
31     D y;
32     if(size(o->ch[0])>=k)
33         {y=Split(o->ch[0],k);o->ch[0]=y.second;o->tain();y.second=o;}
34     else
35         {y=Split(o->ch[1],k-size(o->ch[0])-1);o->ch[1]=y.first;o->tain();y.first=o;}
36     return y;
37 }
38 int Getkth(Treap *o,int v)
39 {
40     if(o==NULL)return 0;
41     return(o->val>=v)?Getkth(o->ch[0],v):Getkth(o->ch[1],v)+size(o->ch[0])+1;
42 }
43 inline int Findkth(int k)
44 {
45     D x=Split(root,k-1);
46     D y=Split(x.second,1);
47     Treap *ans=y.first;
48     root=Merge(Merge(x.first,ans),y.second);
49     return ans!=NULL?ans->val:0;
50 }
51 inline void Insert(int v)
52 {
53     int k=Getkth(root,v);
54     D x=Split(root,k);
55     Treap *o=new Treap(v);
56     root=Merge(Merge(x.first,o),x.second);
57 }
58 void Delete(int v)
59 {
60     int k=Getkth(root,v);
61     D x=Split(root,k);
62     D y=Split(x.second,1);
63     root=Merge(x.first,y.second);
64 }
65 int main(){
66     int m,opt,x;scanf("%d",&m);
67     while(m--)
68     {
69         scanf("%d%d",&opt,&x);
70         switch(opt)
71         {
72             case 1:Insert(x);break;
73             case 2:Delete(x);break;
74             case 3:printf("%d\n",Getkth(root,x)+1);break;
75             case 4:printf("%d\n",Findkth(x));break;
76             case 5:printf("%d\n",Findkth(Getkth(root,x)));break;
77             case 6:printf("%d\n",Findkth(Getkth(root,x+1)+1));break;
78         }
79     }
80 }
时间: 2024-10-11 05:12:49

[您有新的未分配科技点]无旋treap:从好奇到入门(例题:bzoj3224 普通平衡树)的相关文章

[您有新的未分配科技点]可,可,可持久化!?------可持久化线段树普及版讲解

最近跑来打数据结构,于是我决定搞一发可持久化,然后发现--一发不可收啊-- 对于可持久化数据结构,其最大的特征是"历史版本查询",即可以回到某一次修改之前的状态,并继续操作:而这种"历史版本查询"会衍生出其他一些强大的操作. 今天,我们主要讲解可持久化线段树.其实,它的另外一个名字"主席树"似乎更加为人所知(主席%%%). 主席树与普通的线段树相比,多出来的操作是在修改时复制修改的一条链,这个操作的过程大概长下面这样. 至于为什么要这样做-- 对

[您有新的未分配科技点]可,可,可持久化!?------0-1Trie和可持久化Trie普及版讲解

这一次,我们来了解普通Trie树的变种:0-1Trie以及在其基础上产生的可持久化Trie(其实,普通的Trie也可以可持久化,只是不太常见) 先简单介绍一下0-1Trie:一个0-1Trie节点只有两个子节点,分别代表0和1:从根节点开始,第一层代表限制的最高位,依次往下直到最底层,代表二进制第0位. 0-1Trie上的一条链所表示的数字,就是Trie树中的一个数字.0-1Trie除了节点和插入方式与普通的Trie树略有不同之外,其他操作都是和Trie树完全一样的.在维护这个节点插入过的siz

[您有新的未分配科技点]博弈论进阶:似乎不那么恐惧了…… (SJ定理,简单的基础模型)

这次,我们来继续学习博弈论的知识.今天我们会学习更多的基础模型,以及SJ定理的应用. 首先,我们来看博弈论在DAG上的应用.首先来看一个小例子:在一个有向无环图中,有一个棋子从某一个点开始一直向它的出点移动,双方轮流操作,无法操作者输,问是否先手必胜. 考虑一下我们之前的Nim游戏,如果我们把后继状态看成后继点的话,不难发现Nim游戏的互相转移也是一个DAG.因此,DAG上出度为0的点的sg值为0,再用上一篇博客提到的mex操作来求每个点的值就可以了(注意,这并不是一个"大"子图,不能

[您有新的未分配科技点]数位dp:从懵X到板子

数位dp主要用来处理一系列需要数数的问题,一般套路为"求[l,r]区间内满足要求的数/数位的个数" 要求五花八门--比如"不出现某个数字序列","某种数的出现次数"等等-- 面对这种数数题,暴力的想法是枚举每个数,判断是否满足条件 比如这样: #include<cstdio> using namespace std; typedef long long LL; LL l,r,cnt; int main() { scanf("

[您有新的未分配科技点]数位DP:从板子到基础

只会统计数位个数或者某种"符合简单规律"的数并不够--我们需要更多的套路和应用 数位dp中常用的思想是"分类讨论"思想.下面我们就看一道典型的分类讨论例题 1026: [SCOI2009]windy数 Time Limit: 1 Sec  Memory Limit: 162 MB Description windy定义了一种windy数.不含前导零且相邻两个数字之差至少为2的正整数被称为windy数. windy想知道,在A和B之间,包括A和B,总共有多少个wind

[您有新的未分配科技点][BZOJ3545&amp;BZOJ3551]克鲁斯卡尔重构树

这次我们来搞一个很新奇的知识点:克鲁斯卡尔重构树.它也是一种图,是克鲁斯卡尔算法求最小生成树的升级版首先看下面一个问题:BZOJ3545 Peaks. 在Bytemountains有N座山峰,每座山峰有他的高度h_i.有些山峰之间有双向道路相连,共M条路径,每条路径有一个困难值,这个值越大表示越难走. 现在有Q组询问,每组询问询问从点v开始只经过困难值小于等于x的路径所能到达的山峰中第k高的山峰,如果无解输出-1.N<=1e5,M,Q<=5*1e5 上面这个题没有要求在线,因此我们可以离线构造

[您有新的未分配科技点]计算几何入门(1):点,向量以及向量的简单应用

在打了一阵数据结构之后,老师表示"今天晚上让学长给你们讲一下计算几何"--然后就死了.jpg 昨天晚上一直在推数学的式子以及回顾讲课的笔记--计算几何特点就是多而杂,即使是入门部分也是如此-- 首先,我们从二维的几何问题开始处理. 我们知道,高中解析几何计算几何的基础是向量(Vector)和点(Point),所以我们先来表示这两个概念: 在计算几何中,点和向量一般用结构体来存储,像这样: 1 struct Point 2 { 3 double x,y,rad; 4 Point(doub

[BZOJ3223]文艺平衡树 无旋Treap

3223: Tyvj 1729 文艺平衡树 Time Limit: 10 Sec  Memory Limit: 128 MB Description 您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:翻转一个区间,例如原有序序列是5 4 3 2 1,翻转区间是[2,4]的话,结果是5 2 3 4 1 Input 第一行为n,m n表示初始序列有n个数,这个序列依次是(1,2--n-1,n)  m表示翻转操作次数接下来m行每行两个数[l,r] 数据保证 1<=l<

【算法学习】Fhq-Treap(无旋Treap)

Treap--大名鼎鼎的随机二叉查找树,以优异的性能和简单的实现在OIer们中广泛流传. 这篇blog介绍一种不需要旋转操作来维护的Treap,即无旋Treap,也称Fhq-Treap. 它的巧妙之处在于只需要分离和合并两种基本操作,就能实现任意的平衡树常用修改操作. 而不需要旋转的特性也使编写代码时不需要考虑多种情况和复杂的父亲儿子关系的更新,同时降低了时间复杂度. 此外,它还可以方便地支持可持久化,实在是功能强大. 接下来系统性地介绍一下无旋Treap的原理和实现,最后讲解一下应用和例题.