听说很对劲的太刀流不止会splay一种平衡树,并不对劲的片手流为了反驳他,并与之针锋相对,决定学学高端操作。

很对劲的太刀流->

据说splay常数极大,但是由于只知道splay一种平衡树能对序列进行操作,或者进行分裂合并,还不能不写它。

那么常数略小的treap能否对序列操作或者分裂合并呢?想必是能的。

这就是fhq大神的可分裂与合并的treap(据说国家队人手一个自创算法?那必须的不然ctsc论文答辩怎么过)。

它并没有比treap多出什么,反而省了好多事。核心操作只有split和merge。假设每个点有两个数key和w,平衡树维护的是key的性质,小根堆维护的是w的性质。

split(A,k):将A子树中排名不超过k的分一堆,排名超过k的分一堆。要返回两个值,分别是这两个平衡树的根。因为可能有多次祖父、父、子不共线的情况。

void spl(int u,int &l,int &r,int k)
{//称1st-kth为"前一部分",剩下的为"后一部分"
if(!k) l=0,r=u;//排名为0,显然所有数都在后一部分
else if(k==siz[u]) l=u,r=0;//排名与子树大小相等,显然所有数都在前一部分
else if(k<=siz[ls]) r=u,spl(ls,l,ls,k),up(u);//排名小于左子树大小说明右子树全是后一部分,那么还需要划分的就是前一部分
else l=u,spl(rs,rs,r,k-siz[ls]-1), up(u);//和上一行相反
}//会发现它们都是对称的

merge(A,B):类似可并堆。将A与B合并时(假设B中所有key都比A中所有key大),将w更大的合并到w更小的子树上。在这里为了满足二叉搜索树的性质,B合并到A上时要并道A的右儿子(key值都比A点大),A合并到B上时则反之。

void mrg(int &u,int l,int r)
{// l中所有数都小于r中所有数
if(!l || !r)u=l+r;//类似可并堆,有一边是空的就可以直接接过去了
else if(w[l]<w[r])u=l,mrg(rs,rs,r),up(u);//这是小根堆,所以是让w更小的当根
else u=r,mrg(ls,l,ls),up(u);//可并堆是和尽量和更小的儿子合并,但是还要满足平衡树的性质,所以l只能和r的左儿子,r只能和l的右儿子合并
}

其它操作都可以用这两个拼起来,比如insert就是在要插入的位置split,再将插入的数分别与左、右两边合并;delete就是在要删除的位置前后split,再将左右两边merge,中间就不要了。

但是取k数排名(rank)和第k大的数(kth)和splay不大一样。

rank:splay中,这个操作很方便,直接把k转到根就行了。而fhq treap中,不知道排名无法split,只能从上往下找了。

int rank(int u, int k)
{//求出的是小于k的数共有多少个 ,和splay里没什么区别
if(!u) return 0;
if(key[u]>=k) return rank(ls,k);//要注意可能会有一些点的权值相等,如果它们的权值等于k就不大妙了
return rank(rs,k)+siz[ls]+1;//这个函数求严格小于k的数有多少个的,所以比k大的数和等于k的数的处理方式是相同的
}

kth:splay中,取第k大数比较麻烦,因为只能将某个值的数转到根。但是fhq treap中就比较方便,将排名不超过k的split出来后,在split出排名不超过k-1的。剩下的就是排名为k的。

inline int kth(int k)
{
int x,y,z,ans;
spl(rt,x,y,k),spl(x,z,x,k-1),ans=key[x],mrg(x,z,x),mrg(rt,x,y);
return ans;//split之后记得还原
}

普通平衡树的muti-set听上去很烦,但是fhq treap可以有多个相同的点。

这样听上去前驱后继都会变得难求。想必是可以通过巧妙使用split解决的。

prefix:看注释吧

inline int pre(int k)
{
int x,y,z,ans,rk=rank(rt,k);//第一次split后,x中的是所有小于k的;第二次split刚好分出所有小于x的中最大的
spl(rt,x,y,rk),spl(x,z,x,rk-1),ans=key[x],mrg(x,z,x),mrg(rt,x,y);
return ans;
}

suffix:看注释吧

inline int suc(int k)
{
int x,y,z,ans,rk=rank(rt,k+1);//第一次split后,x中是所有小于k+1(小于等于k)的和一个大于等于k+1的;
spl(rt,x,y,rk+1),spl(x,z,x,rk),ans=key[x],mrg(x,z,x),mrg(rt,x,y);//第二次split刚好把那个最大的分出来
return ans;//前驱和后继挺对称的
}

令人不大愉快的是,其实无旋treap的常数和splay差不多。

令人愉快的是,由于没有旋转操作,它是可以可持久化的(不知道比替罪羊树高到哪里去了),而且不用记录父亲。

 #include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iomanip>
#include<iostream>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#define rep(i,x,y) for(register int i=(x);i<=(y);i++)
#define dwn(i,x,y) for(register int i=(x);i>=(y);i--)
#define ls son[u][0]
#define rs son[u][1]
#define maxn 100010
#define inf 2147483647
#define s0 siz[0]=0
using namespace std;
int n,m,x,y,z,rt,cnt,tmp,opt;
int w[maxn],key[maxn],siz[maxn],son[maxn][];
inline int read()
{
int x=,f=;
char ch=getchar();
while(isdigit(ch)== && ch!='-')ch=getchar();
if(ch=='-')f=-,ch=getchar();
while(isdigit(ch))x=x*+ch-'',ch=getchar();
return x*f;
}
inline void write(int x)
{
int f=;char ch[];
if(!x){puts("");return;}
if(x<){putchar('-');x=-x;}
while(x)ch[++f]=x%+'',x/=;
while(f)putchar(ch[f--]);
putchar('\n');
}
inline void res(int &u, int k){w[u=++cnt]=rand()<<|rand(),key[u]=k,siz[u]=;}
inline void up(int u) {s0,siz[u]=siz[ls]+siz[rs]+,s0;}
void mrg(int &u,int l,int r)
{// l中所有数都小于r中所有数
if(!l || !r)u=l+r;//类似可并堆,有一边是空的就可以直接接过去了
else if(w[l]<w[r])u=l,mrg(rs,rs,r),up(u);//这是小根堆,所以是让w更小的当根
else u=r,mrg(ls,l,ls),up(u);//可并堆是和尽量和更小的儿子合并,但是还要满足平衡树的性质,所以l只能和r的左儿子,r只能和l的右儿子合并
}
void spl(int u,int &l,int &r,int k)
{//称1st-kth为"前一部分",剩下的为"后一部分"
if(!k) l=,r=u;//排名为0,显然所有数都在后一部分
else if(k==siz[u]) l=u,r=;//排名与子树大小相等,显然所有数都在前一部分
else if(k<=siz[ls]) r=u,spl(ls,l,ls,k),up(u);//排名小于左子树大小说明右子树全是后一部分,那么还需要划分的就是前一部分
else l=u,spl(rs,rs,r,k-siz[ls]-), up(u);//和上一行相反
}//会发现它们都是对称的
int rank(int u, int k)
{//求出的是小于k的数共有多少个 ,和splay里没什么区别
if(!u) return ;
if(key[u]>=k) return rank(ls,k);//要注意可能会有一些点的权值相等,如果它们的权值等于k就不大妙了
return rank(rs,k)+siz[ls]+;//这个函数求严格小于k的数有多少个的,所以比k大的数和等于k的数的处理方式是相同的
}
inline void insert(int k)
{
int x,y,rk=rank(rt,k);//x中的数是严格小于k的,y中的数是大于等于k的
spl(rt,x,y,rk),res(tmp,k),mrg(x,x,tmp),mrg(rt,x,y);
}
inline void del(int k)
{
int x,y,z,rk=rank(rt,k)+;//x中只有一个k,其余都是严格小于k的
spl(rt,x,y,rk),spl(x,x,z,rk-),mrg(rt,x,y);
}
inline int kth(int k)
{
int x,y,z,ans;
spl(rt,x,y,k),spl(x,z,x,k-),ans=key[x],mrg(x,z,x),mrg(rt,x,y);
return ans;//split之后记得还原
}
inline int pre(int k)
{
int x,y,z,ans,rk=rank(rt,k);//第一次split后,x中的是所有小于k的;第二次split刚好分出所有小于x的中最大的
spl(rt,x,y,rk),spl(x,z,x,rk-),ans=key[x],mrg(x,z,x),mrg(rt,x,y);
return ans;
}
inline int suc(int k)
{
int x,y,z,ans,rk=rank(rt,k+);//第一次split后,x中是所有小于k+1(小于等于k)的和一个大于等于k+1的;
spl(rt,x,y,rk+),spl(x,z,x,rk),ans=key[x],mrg(x,z,x),mrg(rt,x,y);//第二次split刚好把那个最大的分出来
return ans;//前驱和后继挺对称的
}
int main()
{
srand();//据说这样能让人获得永生
n=read();w[]=key[]=inf;
rep(i,,n)
{
opt=read(),x=read();
if(opt==)insert(x);
if(opt==)del(x);
if(opt==)write(rank(rt, x)+);
if(opt==)write(kth(x));
if(opt==)write(pre(x));
if(opt==)write(suc(x));
}
}

其实有一种神奇的数据结构能够在O(1)的时间内完成所有操作,只不过不支持查询操作。它叫【我知道但我不告诉你】。

并不觉得盲目膜时提到国家集训队是对劲的,理同【弓这么用,威力堪比大剑三蓄】。

推荐一波电教G。

并不对劲的fhq treap的更多相关文章

  1. fhq treap最终模板

    新学习了fhq treap,厉害了 先贴个神犇的版, from memphis /* Treap[Merge,Split] by Memphis */ #include<cstdio> # ...

  2. NOI 2002 营业额统计 (splay or fhq treap)

    Description 营业额统计 Tiger最近被公司升任为营业部经理,他上任后接受公司交给的第一项任务便是统计并分析公司成立以来的营业情况. Tiger拿出了公司的账本,账本上记录了公司成立以来每 ...

  3. 【POJ2761】【fhq treap】A Simple Problem with Integers

    Description You have N integers, A1, A2, ... , AN. You need to deal with two kinds of operations. On ...

  4. 【fhq Treap】bzoj1500(听说此题多码上几遍就能不惧任何平衡树题)

    1500: [NOI2005]维修数列 Time Limit: 10 Sec  Memory Limit: 64 MBSubmit: 15112  Solved: 4996[Submit][Statu ...

  5. 「FHQ Treap」学习笔记

    话说天下大事,就像fhq treap —— 分久必合,合久必分 简单讲一讲.非旋treap主要依靠分裂和合并来实现操作.(递归,不维护fa不维护cnt) 合并的前提是两棵树的权值满足一边的最大的比另一 ...

  6. FHQ Treap摘要

    原理 以随机数维护平衡,使树高期望为logn级别 不依靠旋转,只有两个核心操作merge(合并)和split(拆分) 因此可持久化 先介绍变量 ; int n; struct Node { int v ...

  7. FHQ Treap小结(神级数据结构!)

    首先说一下, 这个东西可以搞一切bst,treap,splay所能搞的东西 pre 今天心血来潮, 想搞一搞平衡树, 先百度了一下平衡树,发现正宗的平衡树写法应该是在二叉查找树的基础上加什么左左左右右 ...

  8. 在平衡树的海洋中畅游(四)——FHQ Treap

    Preface 关于那些比较基础的平衡树我想我之前已经介绍的已经挺多了. 但是像Treap,Splay这样的旋转平衡树码亮太大,而像替罪羊树这样的重量平衡树却没有什么实际意义. 然而类似于SBT,AV ...

  9. 浅谈fhq treap

    一.简介 fhq treap 与一般的treap主要有3点不同 1.不用旋转 2.以merge和split为核心操作,通过它们的组合实现平衡树的所有操作 3.可以可持久化 二.核心操作 代码中val表 ...

随机推荐

  1. [转]GitHub 优秀的 Android 开源项目

    GitHub 优秀的 Android 开源项目 主要介绍那些不错个性化的View,包括ListView.ActionBar.Menu.ViewPager.Gallery.GridView.ImageV ...

  2. C语言基本概念之表达式

    原文地址:http://blog.csdn.net/astrotycoon/article/details/50857326 [侵删] 什么是表达式(表达式的定义)? 对于表达式的定义,好像从来没有人 ...

  3. oc温习八:static、extern、const 的了解

    参考文章:http://www.cocoachina.com/ios/20161110/18035.html 1.const 这个单词翻译成中文是“常量”的意思.在程序中我们知道“常量”的值是不能变的 ...

  4. python学习之-- importlib模块

    importlib 模块 Python提供了importlib包作为标准库的一部分.目的就是提供Python中import语句的实现(以及__import__函数).另外,importlib允许程序员 ...

  5. Treasure Hunt--poj1066(最短路加判断线段的关系)

    http://poj.org/problem?id=1066 题目大意:有n条线段 他们都在这个房间里   最后有一个点代表起始位置 现在想通过墙出去  他只能爆破每个房间的中点的门   问最少的门通 ...

  6. Spring4 基本使用

    前言 虽然现在基本上是 springboot 的天下了,但是传统的 spring4 在广大的软件企业中仍然占据很大比例.一上手能用,但是要让我从无到有搭一个spring4的开发环境出来,可能会磕磕碰碰 ...

  7. asterisk 相关数据库配置 使用

    Linux/Unix下ODBC的安装: 先下载最新的unixODBC源码包(http://www.unixodbc.org/unixODBC-2.2.1.tar.gz)放到/usr/local下,然后 ...

  8. Meteor Assets资源

    静态服务器资源位于应用程序内的 private 子文件夹.在这个例子中,我们将学习如何从简单的JSON文件中使用数据. 第1步 - 创建文件和文件夹 让我们创建一个 private 文件夹并在这个文件 ...

  9. HUNT:一款可提升漏洞扫描能力的BurpSuite漏洞扫描插件

    今天给大家介绍的是一款BurpSuite插件,这款插件名叫HUNT.它不仅可以识别指定漏洞类型的常见攻击参数,而且还可以在BurpSuite中组织测试方法. HUNT Scanner(hunt_sca ...

  10. c++之虚基类初始化

    C++虚基类构造函数下面文章详细介绍C++虚基,所谓C++虚基类:是由最派生类的构造函数通过调用虚基类的构造函数进行初始化的,但前提是要深入理解到底什么是C++虚基类,及他是怎么运行的. 前面讲过,为 ...