无旋树堆(FHQ-Treap)学习笔记
简介
无旋树堆(一般统称 \(\text{FHQ-Treap}\)),是一种平衡树。可以用很少的代码达到很优秀的复杂度。
前置知识:
- 二叉搜索树 \(\text{BST}\)
- \(\text{Treap}\) 基本知识
普通平衡树
例题引入
您需要写一种数据结构,来维护一些整数,其中需要提供以下操作:
- 插入一个整数 \(x\)。
- 删除一个整数 \(x\)(若有多个相同的数,只删除一个)。
- 查询整数 \(x\) 的排名(排名定义为比当前数小的数的个数 \(+1\))。
- 查询排名为 \(x\) 的数(如果不存在,则认为是排名小于 \(x\) 的最大数。保证 \(x\) 不会超过当前数据结构中数的总数)。
- 求 \(x\) 的前驱(前驱定义为小于 \(x\),且最大的数)。
- 求 \(x\) 的后继(后继定义为大于 \(x\),且最小的数)。
本题强制在线,保证所有操作合法(操作 \(2\) 保证存在至少一个 \(x\),操作 \(4,5,6\) 保证存在答案)。
对于 \(100\%\) 的数据,\(1\leq n\leq 10^5\),\(1\leq m\leq 10^6\),\(0\leq a_i,x\lt 2^{30}\)。
准备姿势
const int SIZE = 2e6+5; // 数组大小
int son[SIZE][2]; // 平衡树数组
int val[SIZE],rk[SIZE],siz[SIZE]; // val是权值,rk是随机权值,siz是子树大小
int root,points; // root是树根,points是点数(最后一个节点的编号)
mt19937 randomer(time(0)); // 随机生成器
#define ls(i) (son[(i)][0]) // 左子树
#define rs(i) (son[(i)][1]) // 右子树
void pushup(int i){ // 上推信息,这里是子树大小
siz[i]=siz[ls(i)]+siz[rs(i)]+1;
}
int newnode(int v){ // 新建节点
val[++points]=v; // 权值赋值
rk[points]=randomer(); // 随机生成随机权值
siz[points]=1; // 散点子树大小为1
return points; // 返回节点编号
}
FHQ-Treap 基本操作——分裂(split)
这里的 \(\text{split}\) 是按大小分裂,按权值分裂将会在【文艺平衡树】中讲。
\(\operatorname{split}(p,v,l,r)\) 定义为,将根为 \(p\) 的树,分裂成两个子树 \(l,r\) 使得 \(l\) 的所有点权小于等于 \(v\),\(r\) 中的所有点权大于 \(v\)。
实现也是非常的暴力,由于BST中,左子树小于根,右子树大于根,于是直接判断,如果 \(p\) 的全职 \(\le v\),我们就往右子树递归看看有没有机会(左子树一定满足),否则就往左子树递归。
代码:
void split(int p,int v,int &left,int &right){
if(!p){ // 节点不存在的情况
left=right=0;
return;
}
if(val[p]<=v){
left=p;
split(rs(p),v,rs(left),right);
}
else{
right=p;
split(ls(p),v,left,ls(right));
}
pushup(p);
}
由于最多一条链从根搜到叶子,所以时间复杂度是 \(O(\log n)\) 的。
FHQ-Treap 基本操作——合并(merge)
\(\operatorname{merge}(l,r)\) 指,将 \(l,r\) 两棵树合并,然后返回新的树的树根。
如果打过线段树合并,那么打这一部分的内容会感觉很亲切。
在 \(\text{Treap}\) 中,合并方向取决于随机权值,\(\text{FHQ-Treap}\) 也不例外。
如果 \(l\) 的权值大于 \(r\) 的,那么保留 \(l\) 的左子树,右子树改为 \(\operatorname{merge}(r,\operatorname{rightSon}(l))\)。
如果 \(r\) 的权值大于 \(l\) 的,那么保留 \(r\) 的左子树,右子树改为 \(\operatorname{merge}(l,\operatorname{rightSon}(r))\)。
代码:
int merge(int left,int right){
if(!left||!right){ // 节点不存在的情况
if(left)return left;
else if(right)return right;
else return 0;
}
if(rk[left]<rk[right]){
ls(right)=merge(left,ls(right));
pushup(right);
return right;
}
else{
rs(left)=merge(rs(left),right);
pushup(left);
return left;
}
}
时间复杂度 \(O(\log n)\)。
FHQ-Treap 其他操作——insert
\(\operatorname{insert}(v)\),在平衡树中插入一个数 \(v\)。
我们可以将平衡树分裂成两个部分 \(x,y\),使得 \(x\lt v,y \ge v\)。然后合并回去。
代码:
void insert(int v){
int left=0,right=0;
split(root,v-1,left,right);
root=merge(merge(left,newnode(v)),right);
}
FHQ-Treap 其他操作——remove
\(\operatorname{remove}(v)\),在平衡树中删除元素 \(v\),如果有多个,删除其中的任意一个。
将平衡树分裂成 \(x,y,z\),使得 \(x\lt v,y=v,z\gt v\),将 \(y\) 的根删掉,然后合并 \(x,y,z\)。
代码:
void remove(int v){
int left=0,mid=0,right=0;
split(root,v,left,right);
split(left,v-1,left,mid);
mid=merge(ls(mid),rs(mid));
root=merge(merge(left,mid),right);
}
FHQ-Treap 的其他操作——rank
\(\operatorname{rank}(v)\),查询 \(v\) 在平衡树中的排名。
先将平衡树分裂成 \(x,y\),使得 \(x\lt v,y \ge v\),然后查询 \(x\) 的子树大小,加 \(1\) 就是答案,最后记得合并回去。
代码:
int rnk(int v){
int left=0,right=0,ret;
split(root,v-1,left,right);
ret=siz[left]+1;
root=merge(left,right);
return ret;
}
FHQ-Treap 的其他操作——kth,pre,nxt
由于本人弱,不想讲解。
代码:
int kth(int k){
int now=root;
while(1){
if(k<=siz[ls(now)]){
now=ls(now);
}
else if(k==siz[ls(now)]+1){
return val[now];
}
else{
k-=siz[ls(now)]+1;
now=rs(now);
}
}
}
int pre(int v){
int now=root,ret=0;
while(1){
if(!now){
return ret;
}
else if(v<=val[now]){
now=ls(now);
}
else{
ret=val[now];
now=rs(now);
}
}
}
int next(int v){
int now=root,ret=0;
while(1){
if(!now){
return ret;
}
else if(v>=val[now]){
now=rs(now);
}
else{
ret=val[now];
now=ls(now);
}
}
}
例题代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
namespace FHQTreap{
const int SIZE = 2e6+5;
int son[SIZE][2];
int val[SIZE],rk[SIZE],siz[SIZE],root,points;
mt19937 randomer(time(0));
#define ls(i) (son[(i)][0])
#define rs(i) (son[(i)][1])
void pushup(int i){
siz[i]=siz[ls(i)]+siz[rs(i)]+1;
}
int newnode(int v){
val[++points]=v;
rk[points]=randomer();
siz[points]=1;
return points;
}
void split(int p,int v,int &left,int &right){
if(!p){
left=right=0;
return;
}
if(val[p]<=v){
left=p;
split(rs(p),v,rs(left),right);
}
else{
right=p;
split(ls(p),v,left,ls(right));
}
pushup(p);
}
int merge(int left,int right){
if(!left||!right){
if(left)return left;
else if(right)return right;
else return 0;
}
if(rk[left]<rk[right]){
ls(right)=merge(left,ls(right));
pushup(right);
return right;
}
else{
rs(left)=merge(rs(left),right);
pushup(left);
return left;
}
}
void insert(int v){
int left=0,right=0;
split(root,v-1,left,right);
root=merge(merge(left,newnode(v)),right);
}
void remove(int v){
int left=0,mid=0,right=0;
split(root,v,left,right);
split(left,v-1,left,mid);
mid=merge(ls(mid),rs(mid));
root=merge(merge(left,mid),right);
}
int kth(int k){
int now=root;
while(1){
if(k<=siz[ls(now)]){
now=ls(now);
}
else if(k==siz[ls(now)]+1){
return val[now];
}
else{
k-=siz[ls(now)]+1;
now=rs(now);
}
}
}
int pre(int v){
int now=root,ret=0;
while(1){
if(!now){
return ret;
}
else if(v<=val[now]){
now=ls(now);
}
else{
ret=val[now];
now=rs(now);
}
}
}
int next(int v){
int now=root,ret=0;
while(1){
if(!now){
return ret;
}
else if(v>=val[now]){
now=rs(now);
}
else{
ret=val[now];
now=ls(now);
}
}
}
int rnk(int v){
int left=0,right=0,ret;
split(root,v-1,left,right);
ret=siz[left]+1;
root=merge(left,right);
return ret;
}
}
namespace solution{
int n,m,lastans,ret;
int solution(){
lastans=0;
ret=0;
cin>>n>>m;
for(int i=1,v;i<=n;i++){
cin>>v;
FHQTreap::insert(v);
}
while(m--){
int op,v;
cin>>op>>v;
if(op==1){
v^=lastans;
FHQTreap::insert(v);
}
else if(op==2){
v^=lastans;
FHQTreap::remove(v);
}
else if(op==3){
v^=lastans;
lastans=FHQTreap::rnk(v);
ret^=lastans;
}
else if(op==4){
v^=lastans;
lastans=FHQTreap::kth(v);
ret^=lastans;
}
else if(op==5){
v^=lastans;
lastans=FHQTreap::pre(v);
ret^=lastans;
}
else{
v^=lastans;
lastans=FHQTreap::next(v);
ret^=lastans;
}
}
cout<<ret;
return 0;
}
}
signed main(){
ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
return solution::solution();
}
普通平衡树例题
P2343 宝石管理系统
P2343 宝石管理系统
GY 君购买了一批宝石放进了仓库。有一天 GY 君心血来潮,想要清点他的宝石,于是把 \(m\) 个宝石都取出来放进了宝石管理系统。每个宝石 \(i\) 都有一个珍贵值 \(v_i\),他希望你能编写程序查找到从大到小第 \(n\) 珍贵的宝石。但是现在问题来了,他非常不小心的留了一些宝石在仓库里面,有可能要往现有的系统中添加宝石。这些宝石的个数比较少。他表示非常抱歉,但是还是希望你的系统能起作用。\(m\leq 100000\),\(q\leq 30000\)
这道题比较水,直接 \(\text{FHQ-Treap}\) 模拟。
时间复杂度 \(O(q\log m)\)
P2073 送花
P1503 鬼子进村
P3871 [TJOI2010]中位数
无旋树堆(FHQ-Treap)学习笔记的更多相关文章
- 左偏树 / 非旋转treap学习笔记
背景 非旋转treap真的好久没有用过了... 左偏树由于之前学的时候没有写学习笔记, 学得也并不牢固. 所以打算写这么一篇学习笔记, 讲讲左偏树和非旋转treap. 左偏树 定义 左偏树(Lefti ...
- fhq treap 学习笔记
序 今天心血来潮,来学习一下fhq treap(其实原因是本校有个OIer名叫fh,当然不是我) 简介 fhq treap 学名好像是"非旋转式treap及可持久化"...听上去怪 ...
- FHQ treap学习(复习)笔记
.....好吧....最后一篇学习笔记的flag它倒了..... 好吧,这篇笔记也鸽了好久好久了... 比赛前刷模板,才想着还是补个坑吧... FHQ,这个神仙(范浩强大佬),发明了这个神仙的数据结构 ...
- treap学习笔记
treap是个很神奇的数据结构. 给你一个问题,你可以解决它吗? 这个问题需要treap这个数据结构. 众所周知,二叉查找树的查找效率低的原因是不平衡,而我们又不希望用各种奇奇怪怪的旋转来使它平衡,那 ...
- fhq treap抄袭笔记
目录 碎碎念 点一下 注意!!! 模板 fhq treap 碎碎念 我咋感觉合并这么像左偏树呢 ps:难道你们的treap都是小头堆的吗 fhq真的是神人 现在看以前学的splay是有点恶心,尤其是压 ...
- 树堆(Treap)学习笔记 2020.8.12
如果一棵二叉排序树的节点插入的顺序是随机的,那么这样建立的二叉排序树在大多数情况下是平衡的,可以证明,其高度期望值为 \(O( \log_2 n )\).即使存在一些极端情况,但是这种情况发生的概率很 ...
- Treap + 无旋转Treap 学习笔记
普通的Treap模板 今天自己实现成功 /* * @Author: chenkexing * @Date: 2019-08-02 20:30:39 * @Last Modified by: chenk ...
- 树堆(Treap)
平衡树 简介: 平衡二叉树(Balanced Binary Tree)具有以下性质:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树.平衡二叉树的常用实现方 ...
- [Treap][学习笔记]
平衡树 平衡树就是一种可以在log的时间复杂度内完成数据的插入,删除,查找第k大,查询排名,查询前驱后继以及其他许多操作的数据结构. Treap treap是一种比较好写,常数比较小,可以实现平衡树基 ...
- 矩阵树定理(Matrix Tree)学习笔记
如果不谈证明,稍微有点线代基础的人都可以在两分钟内学完所有相关内容.. 行列式随便找本线代书看一下基本性质就好了. 学习资源: https://www.cnblogs.com/candy99/p/64 ...
随机推荐
- Vue3 SFC 和 TSX 方式调用子组件中的函数
在开发中会遇到这样的需求:获取子组件的引用,并调用子组件中定义的方法.如封装了一个表单组件,在父组件中需要调用这个表单组件的引用,并调用这个表单组件的校验表单函数或重置表单函数.要实现这个功能,首先要 ...
- Vue学习之--------内置指令的使用【v-bind、v-model、v-for、v-on、v-if 、v-else、v-show、v-text。。。】(2022/7/19)
文章目录 1.常见的内置指令 2.代码实例 3.测试效果 1.常见的内置指令 v-bind: 单向绑定解析表达式, 可简写为 :xxx v-model: 双向数据绑定 v-for : 遍历数组/对象/ ...
- Vue学习之--------脚手架的分析、Ref属性、Props配置(2022/7/28)
欢迎大家加入我的社区:http://t.csdn.cn/Q52km 社区中不定时发红包 文章目录 1.脚手架的分析 2.ref属性 2.1 基础知识 2.2 代码实现 2.3 测试效果 3.Props ...
- 驱动开发:内核枚举Registry注册表回调
在笔者上一篇文章<驱动开发:内核枚举LoadImage映像回调>中LyShark教大家实现了枚举系统回调中的LoadImage通知消息,本章将实现对Registry注册表通知消息的枚举,与 ...
- 知识图谱-生物信息学-医学顶刊论文(Bioinformatics-2021)-MSTE: 基于多向语义关系的有效KGE用于多药副作用预测
MSTE: 基于多向语义关系的有效KGE用于多药副作用预测 论文标题: Effective knowledge graph embeddings based on multidirectional s ...
- SQL中的Convert()函数方法(转换数据格式)
Convert函数的使用方法 格式: convert(data_type(length),data_to_be_converted,style) data_type(length)转换的目标数据类型, ...
- 【Azure 事件中心】Event Hub 无法连接,出现 Did not observe any item or terminal signal within 60000ms in 'flatMapMany' 的错误消息
问题描述 使用Java SDK连接Azure Event Hub,一直出现 java.util.concurrent.TimeoutException 异常, 消息为:java.util.concur ...
- 用Nodejs 实现一个简单的 Redis客户端
目录 0. 写在前面 1. 背景映入 2. 数据库选择 3. Nodejs TCP连接 3. 代码编写 4. 实验 5. wireshark 抓包分析 6. 杂与代码 0. 写在前面 大家如果有去看过 ...
- JIRA操作之 基本说明
官方说明:https://docs.atlassian.com/software/jira/docs/api/7.6.1/ 项目(Project) Project是一组问题单(Issue)的集合,每个 ...
- Java判断质数/素数的三种方法
介绍 质数:在大于1的整数中,如果只包含1和本身这两个约数,就被称为质数(素数) 解法 解法一:暴力枚举 枚举从2 ~ N的每一个数 实际上不用枚举到N,只需要枚举到√N就行 注意: 不要使用sqrt ...