听说很对劲的太刀流不止会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. CodeForce 448C 木片填涂问题

    题目大意:有多片木片需要填涂,可以每次横着涂一行,也可以一次涂一列,当然你涂一行时遇到中间长度不够高的木片,填涂到此中断 这题目运用dfs能更容易的解出,虽然还是十分不容易理解 #include &l ...

  2. SSH日志位置

    # Redhat or Fedora Core: /var/log/secure # Mandrake, FreeBSD or OpenBSD: /var/log/auth.log # SuSE: / ...

  3. Python基础教程笔记——第7章:更加抽象(类)

    下面进入Python的面向对象: 对象的魔力: 多态:---可以对不同类的对象使用同样的操作 封装:---对外部隐藏对象内部的工作方式 继承:---以普通的类为基础建立专门的类对象 (1)多态: is ...

  4. 我的Github,个人博客

    Github: github.com/wuxinwei 个人博客: blog.wuxinwei.org

  5. Python基础之 一 集合(set)

    集合:是一个无序的,不重复的数据组合.主要作用: 去重(把列表变成集合就自动去重) 关系测试 测试俩组数据的交集,差集,并集等关系 关系测试共有7种,如下: 名称 方法名 简写符号 解释交集 s.in ...

  6. POJ 1797 【一种叫做最大生成树的很有趣的贪心】【也可以用dij的变形思想~】

    题意: 给一个无向图,找1到n所有的路中每条路最小权值的最大值! 屌丝一开始的思想是利用dij的变形~ 但是==屌丝忘记了更新dis数组~结果TLE无数次... 说正经的~dij的变形思想是这样的if ...

  7. 寒武纪camp Day5

    补题进度:6/10 A(状压dp) 题意: 有n个数字1,2,...,n,有m个限制(a,b),表示至少要有一个数字a排在数字b的前面 你需要构造出一个含有数字1~n的序列,数字可以重复多次,要求该序 ...

  8. Java模拟斗地主(实现大小排序)

    import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Li ...

  9. spring mvc 集成freemarker模板

    主要使用到的jar 文件:spring mvc +freemarker.jar 第一步:spring mvc 集成 freemarker <!-- 定义跳转的文件的前后缀 ,视图模式配置--&g ...

  10. RHEL 启动系统及故障排除

    一:Linux的启动过程: 开机加电自检->MBR引导(boot loader占446字节,分区列表64字节,magic占2字节)-->grub菜单(MBR是grub的第一个字段,第二个字 ...