fhq treap 学习笔记
序
今天心血来潮,来学习一下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 学习笔记的更多相关文章
- FHQ treap学习(复习)笔记
.....好吧....最后一篇学习笔记的flag它倒了..... 好吧,这篇笔记也鸽了好久好久了... 比赛前刷模板,才想着还是补个坑吧... FHQ,这个神仙(范浩强大佬),发明了这个神仙的数据结构 ...
- 左偏树 / 非旋转treap学习笔记
背景 非旋转treap真的好久没有用过了... 左偏树由于之前学的时候没有写学习笔记, 学得也并不牢固. 所以打算写这么一篇学习笔记, 讲讲左偏树和非旋转treap. 左偏树 定义 左偏树(Lefti ...
- treap学习笔记
treap是个很神奇的数据结构. 给你一个问题,你可以解决它吗? 这个问题需要treap这个数据结构. 众所周知,二叉查找树的查找效率低的原因是不平衡,而我们又不希望用各种奇奇怪怪的旋转来使它平衡,那 ...
- fhq treap抄袭笔记
目录 碎碎念 点一下 注意!!! 模板 fhq treap 碎碎念 我咋感觉合并这么像左偏树呢 ps:难道你们的treap都是小头堆的吗 fhq真的是神人 现在看以前学的splay是有点恶心,尤其是压 ...
- Treap + 无旋转Treap 学习笔记
普通的Treap模板 今天自己实现成功 /* * @Author: chenkexing * @Date: 2019-08-02 20:30:39 * @Last Modified by: chenk ...
- [Treap][学习笔记]
平衡树 平衡树就是一种可以在log的时间复杂度内完成数据的插入,删除,查找第k大,查询排名,查询前驱后继以及其他许多操作的数据结构. Treap treap是一种比较好写,常数比较小,可以实现平衡树基 ...
- Treap-平衡树学习笔记
平衡树-Treap学习笔记 最近刚学了Treap 发现这种数据结构真的是--妙啊妙啊~~ 咳咳.... 所以发一发博客,也是为了加深蒟蒻自己的理解 顺便帮助一下各位小伙伴们 切入正题 Treap的结构 ...
- 「FHQ Treap」学习笔记
话说天下大事,就像fhq treap —— 分久必合,合久必分 简单讲一讲.非旋treap主要依靠分裂和合并来实现操作.(递归,不维护fa不维护cnt) 合并的前提是两棵树的权值满足一边的最大的比另一 ...
- 「学习笔记」 FHQ Treap
FHQ Treap FHQ Treap (%%%发明者范浩强年年NOI金牌)是一种神奇的数据结构,也叫非旋Treap,它不像Treap zig zag搞不清楚(所以叫非旋嘛),也不像Splay完全看不 ...
随机推荐
- MT【190】绝对值的和
(2012浙江压轴题)已知$a>0,b\in R$,函数$f(x)=4ax^3-2bx-a+b$.1)证明:当$0\le x\le 1$时,i)函数$f(x)$的最大值为$|2a-b|+a;$i ...
- 搜索引擎(Solr-搜索详解)
学习目标 1.掌握SOLR的搜索工作流程: 2.掌握solr搜索的表示语法及查询解析器 3.熟悉solr搜索的JSON格式 API Solr搜索流程介绍 回顾,使用 lucene进行搜索的步骤: So ...
- 【转】#pragma的用法
在所有的预处理指令中,#Pragma 指令可能是最复杂的了,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作.#pragma指令对每个编译器给出了一个方法,在保持与C和C++语言完全兼容的 ...
- POJ 3281 Dining (网络流)
POJ 3281 Dining (网络流) Description Cows are such finicky eaters. Each cow has a preference for certai ...
- bug找到吐的赶脚
bug找到吐的赶脚,真**刺激 一.单元测试 设计思路 首先是需要写一个无括号四则运算函数 下面的运算先是运算括号内的数 然后将null后置 全部代码测试,覆盖率92.4% 二.结构优化 uml图 流 ...
- spring@Transactional的一点理解
spring事务有7种传播行为,分别是: 1.PROPAGATION.REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置. 2.PROPAGAT ...
- Zabbix应用七:Zabbix发送短信报警
Zabbix利用Python脚本调用短信API发送报警信息 一.先贴出python脚本: #!/usr/bin/python # _*_ coding:utf8 _*_ import sys impo ...
- 人生效率手册:如何卓有成效地过好每一天--By张萌姐姐--读书笔记
读书笔记:<人生效率手册>:如何卓有成效地过好每一天--By张萌姐姐... 整本书看完的感受: 这本书主要讲的是生活中我们需要给自己一个目标,然后通过自己的努力去实现这个目标,书中说的很多 ...
- Mac OS利用ssh访问ubuntu虚拟机及云端操作
1.桥接模式 将该虚拟机的网口设置成桥接模式(Bridged Adapter),以确保主机可以ping通虚拟机: 2.安装ssh 在ubuntu虚拟机上安装ssh server: sudo apt-g ...
- php银行卡校验
前言银行金卡,维萨和万事达.银联品牌,如果是贷记卡或准贷记卡,一定为16位卡号.而借记卡可以16-19位不等.美国运通卡则不论金卡或是白金卡.普通卡,都是15位卡号.16-19 位卡号校验位采用 Lu ...