今天心血来潮,来学习一下fhq treap(其实原因是本校有个OIer名叫fh,当然不是我)

简介

fhq treap 学名好像是“非旋转式treap及可持久化”。。。听上去怪怪的。其实就是可以代替LCT、BST等等码量很高的东东。

定义

struct node{
int son[2],val,rand_val,sz;//很好理解,从左到右依次为:左右儿子编号,权值,随机权值(用处后面会讲),此节点下(包括此节点)共有多少个节点
}tr[N];

操作

最基本的操作

其实都不应该叫做操作。应该类似于维护的。。。

void update(int x){
tr[x].sz=tr[tr[x].son[0]].sz+tr[tr[x].son[1]].sz+1;//更新节点个数,将左右子树节点个数+本节点(不要忘了。。QWQ)
}
int new_node(int v){
tot++;//总节点编号++
tr[tot].sz=1;//默认为1(叶子)
tr[tot].val=v;//权值赋值
tr[tot].rand_val=rand();//随机rand权值
return tot;//返回节点编号
}

基本操作

其实就三个啦。。。。

操作1:merge(默认x<y)

merge的操作其实就是把两棵树合并成一棵(真好!)。使用递归。按照rand出来的权值进行判断是左子树还是右子树。

int merge(int x,int y){
if(!x||!y) return x+y;//如果有一棵树是空的,那么就可以直接退出
if(tr[x].rand_val<tr[y].rand_val){//按照rand的权值确定左右子树
tr[x].son[1]=merge(tr[x].son[1],y);//将x的右儿子中merge树y
update(x);//更新节点数
return x;//返回
}else{//同理
tr[y].son[0]=merge(x,tr[y].son[0]);
update(y);
return y;
}
}

操作2:split

split的操作其实就是把一棵树拆成两棵子树。当然有两种拆法,一种是按照权值,还有一种是按照节点数分。

这里的操作2是按照权值分,操作3按照节点数分。

void split(int now,int k,int &x,int &y){//以权值k来分now树,成x,y
if(!now) x=y=0;//如果当前操作为空树,返回空子树
else{
if(tr[now].val<=k) x=now,split(tr[now].son[1],k,tr[now].son[1],y);//如果小于或等于权值k,将左子树改为当前的树,并将当前的树的右子树继续往下分(把所有小于等于权值k的节点划分到一棵树当中)
else y=now,split(tr[now].son[0],k,x,tr[now].son[0]);//同理
update(now);//注意更新节点数
}
}

操作3:rank

在操作2中已经说明过了,是按照节点数分(有点像其他数据结构中查找第k名的操作)

int rank(int now,int k){//在now树中,查找以权值排序的第k个节点
while(1){
if(k<=tr[tr[now].son[0]].sz) now=tr[now].son[0];//如果k在左子树当中那就更新
else{
if(k==tr[tr[now].son[0]].sz+1) return now;//正好找到
else{//如果k在右子树当中,注意k还要减去左子树的个数+本节点
k-=tr[tr[now].son[0]].sz+1;
now=tr[now].son[1];
}
}
}
}

普通的操作

拿一道例题来讲吧。。。

传送门

其实操作并没有高级多少(主要是想象力。。。)

操作1:插入\(x\)数

split(root,a,x,y);
root=merge(merge(x,new_node(a)),y);

这个比较好理解,我们先把树分为x,y两部分,然后把新的节点a看做是一棵树,先与x合并,合并完之后将合并的整体与y合并

操作2:删除\(x\)数(若有多个相同的数,因只删除一个)

split(root,a,x,z);
split(x,a-1,x,y);
y=merge(tr[y].son[0],tr[y].son[1]);
root=merge(merge(x,y),z);

首先我们把树分为x和z两部分

那么x树中的最大权值为a

再把x分为x和y两部分。

此时x中的最大权值为a-1,且权值为a的节点一定是y的根节点。

然后我们可以无视y的根节点,直接把y的左右孩子合并起来,这样就成功的删除了根节点,

最后再把x,y,z合并起来就好

操作3:查询\(x\)数的排名(排名定义为比当前数小的数的个数+1。若有多个相同的数,因输出最小的排名)

split(root,a-1,x,y);
printf("%d\n",tr[x].sz+1);
root=merge(x,y);

我们首先按照a-1的权值把树分开。

那么x树中最大的应该是a-1。

那么a的排名就是siz[x]+1

操作4:查询排名为\(x\)的数

//rank函数不是白吃饭的

printf("%d\n",tr[rank(root,a)].val);

不解释了QWQ

操作5:求\(x\)的前驱(前驱定义为小于\(x\),且最大的数)

//rank函数真的不是白吃饭的

split(root,a-1,x,y);
printf("%d\n",tr[rank(x,tr[x].sz)].val);
root=merge(x,y);

因为要小于a,那么我们按照a-1的权值划分,

x中最大的一定是<=a-1的,

所以我们直接输出x中最大的数就好,

(这里有一个小技巧,因为sz储存的是节点的数目,然后根据二叉查找树的性质,编号最大的就是值最大的)

操作6:求\(x\)的后继(后继定义为大于\(x\),且最小的数)

//rank函数真的不是白吃饭的

split(root,a,x,y);
printf("%d\n",tr[rank(y,1)].val);
root=merge(x,y);

因为要大于a,那么我们按照a的权值划分(取子树y),

y中最小的一定是>a的,

所以我们直接输出y中最小的数就好,

(像操作5一样这里有一个小技巧,因为sz储存的是节点的数目,然后根据二叉查找树的性质,编号最小的就是值最小的)

完整的代码:

#include<bits/stdc++.h>
#define N 100010
using namespace std;
inline int read(){
char ch=getchar();int res=0,f=1;
while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9') res=res*10+ch-'0',ch=getchar();
return res*f;
}
inline void write(int x){
if(x<0) putchar('-'),x=-x;
if(x<10) putchar(x+'0');
else{
write(x/10);
putchar(x%10+'0');
}
}
struct node{
int son[2],val,rand_val,sz;
}tr[N];
int tot=0,root=0;
void update(int x){
tr[x].sz=tr[tr[x].son[0]].sz+tr[tr[x].son[1]].sz+1;
}
int new_node(int v){
tot++;
tr[tot].sz=1;
tr[tot].val=v;
tr[tot].rand_val=rand();
return tot;
}
int merge(int x,int y){
if(!x||!y) return x+y;
if(tr[x].rand_val<tr[y].rand_val){
tr[x].son[1]=merge(tr[x].son[1],y);
update(x);
return x;
}else{
tr[y].son[0]=merge(x,tr[y].son[0]);
update(y);
return y;
}
}
void split(int now,int k,int &x,int &y){
if(!now) x=y=0;
else{
if(tr[now].val<=k) x=now,split(tr[now].son[1],k,tr[now].son[1],y);
else y=now,split(tr[now].son[0],k,x,tr[now].son[0]);
update(now);
}
}
int rank(int now,int k){
while(1){
if(k<=tr[tr[now].son[0]].sz) now=tr[now].son[0];
else{
if(k==tr[tr[now].son[0]].sz+1) return now;
else{
k-=tr[tr[now].son[0]].sz+1;
now=tr[now].son[1];
}
}
}
}
int T,type,x,y,z,a,b;
int main(){
srand((unsigned)time(NULL));
scanf("%d",&T);
while(T--){
scanf("%d%d",&type,&a);
if(type==1){
split(root,a,x,y);
root=merge(merge(x,new_node(a)),y);
}else if(type==2){
split(root,a,x,z);
split(x,a-1,x,y);
y=merge(tr[y].son[0],tr[y].son[1]);
root=merge(merge(x,y),z);
}else if(type==3){
split(root,a-1,x,y);
printf("%d\n",tr[x].sz+1);
root=merge(x,y);
}else if(type==4) printf("%d\n",tr[rank(root,a)].val);
else if(type==5){
split(root,a-1,x,y);
printf("%d\n",tr[rank(x,tr[x].sz)].val);
root=merge(x,y);
}else if(type==6){
split(root,a,x,y);
printf("%d\n",tr[rank(y,1)].val);
root=merge(x,y);
}
}
return 0;
}

写在最后

当然,fhqtreap的应用不仅限于这些,还有许多,还待我继续学习。。。

参考:zwfymqz

mocha从两位大佬的博文中窃取了许多。。。

fhq treap 学习笔记的更多相关文章

  1. FHQ treap学习(复习)笔记

    .....好吧....最后一篇学习笔记的flag它倒了..... 好吧,这篇笔记也鸽了好久好久了... 比赛前刷模板,才想着还是补个坑吧... FHQ,这个神仙(范浩强大佬),发明了这个神仙的数据结构 ...

  2. 左偏树 / 非旋转treap学习笔记

    背景 非旋转treap真的好久没有用过了... 左偏树由于之前学的时候没有写学习笔记, 学得也并不牢固. 所以打算写这么一篇学习笔记, 讲讲左偏树和非旋转treap. 左偏树 定义 左偏树(Lefti ...

  3. treap学习笔记

    treap是个很神奇的数据结构. 给你一个问题,你可以解决它吗? 这个问题需要treap这个数据结构. 众所周知,二叉查找树的查找效率低的原因是不平衡,而我们又不希望用各种奇奇怪怪的旋转来使它平衡,那 ...

  4. fhq treap抄袭笔记

    目录 碎碎念 点一下 注意!!! 模板 fhq treap 碎碎念 我咋感觉合并这么像左偏树呢 ps:难道你们的treap都是小头堆的吗 fhq真的是神人 现在看以前学的splay是有点恶心,尤其是压 ...

  5. Treap + 无旋转Treap 学习笔记

    普通的Treap模板 今天自己实现成功 /* * @Author: chenkexing * @Date: 2019-08-02 20:30:39 * @Last Modified by: chenk ...

  6. [Treap][学习笔记]

    平衡树 平衡树就是一种可以在log的时间复杂度内完成数据的插入,删除,查找第k大,查询排名,查询前驱后继以及其他许多操作的数据结构. Treap treap是一种比较好写,常数比较小,可以实现平衡树基 ...

  7. Treap-平衡树学习笔记

    平衡树-Treap学习笔记 最近刚学了Treap 发现这种数据结构真的是--妙啊妙啊~~ 咳咳.... 所以发一发博客,也是为了加深蒟蒻自己的理解 顺便帮助一下各位小伙伴们 切入正题 Treap的结构 ...

  8. 「FHQ Treap」学习笔记

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

  9. 「学习笔记」 FHQ Treap

    FHQ Treap FHQ Treap (%%%发明者范浩强年年NOI金牌)是一种神奇的数据结构,也叫非旋Treap,它不像Treap zig zag搞不清楚(所以叫非旋嘛),也不像Splay完全看不 ...

随机推荐

  1. 【题解】 bzoj1190: [HNOI2007]梦幻岛宝珠 (动态规划)

    bzoj1190,懒得复制,戳我戳我 Solution: 这道题其实是一个背包(分组背包),但是由于数字比较大,就要重新构造dp式子.啃了三天才懂. \(dp[i][j]\)表示背包容积为\(j*2^ ...

  2. NetBeans无法使用svn相关功能

    本来中NetBeans使用subversion功能只要下载过subversion客户端就可以直接使用了,我也一直是这样的,可是今天忽然的不能用了,发现问题是因为我在文件中使用subversion的比较 ...

  3. C++时间标准库时间time

    转自:http://www.cnblogs.com/yukaizhao/archive/2011/04/29/cpp_time_system_time.html (玉开) C++标准库中的时间需要引用 ...

  4. Codeforces 468C/469E 易错点

    #include <stdio.h> #include <stdlib.h> typedef long long ll; int main() { ll x=1e17; ll ...

  5. linux sed文本

    sed介绍 sed(stream editor)是一种非交互式的流编辑器,通过多种转换修改流经它的文本.默认情况下,sed不会改变原文件本身,而只是对流经sed命令的文本进行修改,并将修改后的结果打印 ...

  6. Redis数据类型和常用命令

    Redis相较于其它的数据库虽然简单,但是要熟记所有命令的用法也并非易事.一个简单的技巧是通过要操作的数据类型来将这些命令进行结构化. 数据类型和对应命令 所有存储于redis中的数据都对应于一个键值 ...

  7. virtualenv和virtualenvwrapper介绍和使用

    virtualen介绍 virtualenv优点: 工具可以创建隔离的Python环境 . 环境升级不影响其他应用,也不会影响全局的python环境 它可以防止系统中出现包管理混乱和版本的冲突 vir ...

  8. python---redis管道(事务)和发布订阅

    管道:将数据操作放在内存中,只有成功后,才会一次性全部放入redis #管道(事务),要是都成功则成功,失败一个全部失败 #原理:将数据操作放在内存中,只有成功后,才会一次性全部放入redis pip ...

  9. Jenkins 01——简介

    Jenkins是一个开源软件项目,一个可扩展的持续集成引擎.旨在提供一个开放易用的软件平台,使软件的持续集成变成可能. 持续集成是一种开发实践,需要开发人员定期将代码集成到共享存储库中.这个概念意在消 ...

  10. bzoj千题计划247:bzoj4903: [Ctsc2017]吉夫特

    http://uoj.ac/problem/300 预备知识: C(n,m)是奇数的充要条件是 n&m==m 由卢卡斯定理可以推出 选出的任意相邻两个数a,b 的组合数计算C(a,b)必须是奇 ...