FHQ-treap 即非旋Treap,是一种短小精悍,功能丰富的平衡树。

据说它的效率介于 Treap 和 Splay 之间(可能是我的FHQ常数比较小,跑得比我的Treap还快)。

它可以实现 Splay 可以实现的所有功能,包括平衡树的基本操作和区间翻转操作。

它的实现难度比 Splay 要简单很多,没有 Splay 那么多转来转去的操作,不会令人头晕,而且FHQ代码的对称性良好,易于调试。

有两种 FHQ-Treap,一种是按值为关键字的,一种是按下标为关键字的(适用于区间翻转等操作时)。

FHQ-Treap 类似于Treap,都有一个随机的 key 值,以此来保持树的平衡。

Split (分裂) & merge (合并)

FHQ-Treap 的核心操作为 \(\text{split}\) (分裂) 和 \(\text{merge}\) (合并)。

\(\text{split}(root,val,x,y)\) 表示将 \(root\) 的子树拆分为两半,一半中的值(或下标)都小于等于 \(val\), 一半的值都大于 \(val\)。

\(\text{merge}(x,y)\) 表示将 \(x\) 子树和 \(y\) 子树合并起来,要保证x中的元素都小于y中的元素。

具体操作都是依靠 \(\text{split}\) 和 \(\text{merge}\) 来实现的。

// 按值为关键字
struct Node
{
int l,r; // 左右儿子编号
int val;// 真实值
int key;// 随机值
int size;// 子树大小
}fhq[N]; void pushup(int u){ // 维护其子树大小
fhq[u].size = fhq[fhq[u].l].size + fhq[fhq[u].r].size + 1;
} inline void split(int u,int val,int &x,int &y) // 递归实现
{
if(!u) {// 空节点
x = y = 0;
return;
}
if(fhq[u].val <= val) { // 点的值大于val,递归其右儿子
x = u;
split(fhq[u].r,val,fhq[u].r, y);
}
else { // 点的值小于val,递归其左儿子
y = u;
split(fhq[u].l,val,x,fhq[u].l);
}
pushup(u); // 维护其子树大小
}
inline int merge(int x,int y)
{
if(!x || !y) return x | y; // 有一个子树为空,不需要操作
if(fhq[x].key >= fhq[y].key) { // key 为随机值,这里是保证平衡的关键
fhq[x].r = merge(fhq[x].r,y); // 将y接到右儿子或更下方上,递归合并
pushup(x);
return x;
}
else {
fhq[y].l = merge(x,fhq[y].l);
pushup(y);
return y;
}
}

insert (插入)

// 插入一个值为 val 的点
int create(int val) { // 新建一个值为val的点
fhq[++cnt].val = val,fhq[cnt].key = rnd(),fhq[cnt].size = 1;
return cnt;
}
void insert(int val){
split(root,val,x,y); // 按val分裂成x,y,x中的元素都小于val,以此满足merge需要的性质
root = merge(merge(x,create(val)), y); // 将x,新建的值为val的点,y合并
}

删除

//删除一个值为val的点
void del(int val) {
split(root,val,x,z);
split(x,val-1,x,y); // 前面两行将val点(y)抠出来
y = merge(fhq[y].l,fhq[y].r); // 直接合并y的左节点和右节点,即为删除y
root = merge(merge(x,y),z); // 合并即可
}

按值获取排名

//找到val在树中的排名
void getrank(int val) {
split(root,val-1,x,y); // 抠出小于val的那些点(x)
printf("%d\n",fhq[x].size+1); // x的size即为小于val的点的个数, 加上val自己即为排名
root = merge(x,y);
}

按排名获取值

//获取排名为rank的点的值
void getval(int rank) {
int u = root;
while(u) {
// fhq[fhq[u].l].size为值小于点u的点的个数
if(fhq[fhq[u].l].size + 1 == rank) break; // 找到了!!!
if(fhq[fhq[u].l].size >= rank) u = fhq[u].l; // 发现rank在左儿子中,递归查找左儿子
else rank -= fhq[fhq[u].l].size + 1, u = fhq[u].r; // 发现rank在右儿子中,递归查找右儿子前要减去rank的值
}
printf("%d\n", fhq[u].val);
}

找前驱

// 找到小于val的最大值
void pre(int val) {
split(root,val-1,x,y); // 先抠出小于val的值(x)
int u = x;
while(fhq[u].r) u = fhq[u].r; // 在小于val的值中找最大值
printf("%d\n",fhq[u].val);
root = merge(x,y);
}

找后继

// 找大于val的最小值
void nxt(int val) {
split(root,val,x,y); // 先抠出大于val的值(y)
int u = y;
while(fhq[u].l) u = fhq[u].l; // 在大于val的值中找最小值
printf("%d\n",fhq[u].val);
root = merge(x,y);
}

完整板子

点击查看代码
// P3369 【模板】普通平衡树
// 因为这里加了两个哨兵,所以实现和上面介绍的有一点点区别
#include <bits/stdc++.h>
using namespace std; const int N = 1e5 + 10; struct Node {
int l,r;
int val;// real value
int key;// for treap
int size;
} fhq[N];
int root,cnt; mt19937 rnd(233); int create(int val) {
fhq[++cnt].val = val,fhq[cnt].key = rnd(),fhq[cnt].size = 1;
return cnt;
} void pushup(int u) {
fhq[u].size = fhq[fhq[u].l].size + fhq[fhq[u].r].size + 1;
} void split(int u,int val,int &x,int &y) {
if(!u) {
x = y = 0;
return;
} if(fhq[u].val <= val) {
x = u;
split(fhq[u].r,val,fhq[u].r, y);
} else {
y = u;
split(fhq[u].l,val,x,fhq[u].l);
}
pushup(u);
} int merge(int x,int y) {
if(!x || !y) return x + y;
if(fhq[x].key >= fhq[y].key) {
fhq[x].r = merge(fhq[x].r,y);
pushup(x);
return x;
} else {
fhq[y].l = merge(x,fhq[y].l);
pushup(y);
return y;
}
} int x,y,z;
void insert(int val) {
split(root,val,x,y);
root = merge(merge(x,create(val)), y);
} void del(int val) {
split(root,val,x,z);
split(x,val-1,x,y);
y = merge(fhq[y].l,fhq[y].r);
root = merge(merge(x,y),z);
} void getrank(int val) {
split(root,val-1,x,y);
printf("%d\n",fhq[x].size);
root = merge(x,y);
} void getval(int rank) {
rank++;
int u = root; while(u) {
if(fhq[fhq[u].l].size + 1 == rank) break;
if(fhq[fhq[u].l].size >= rank) u = fhq[u].l;
else rank -= fhq[fhq[u].l].size + 1, u = fhq[u].r;
} printf("%d\n", fhq[u].val);
} void pre(int val) {
split(root,val-1,x,y); int u = x;
while(fhq[u].r) u = fhq[u].r; printf("%d\n",fhq[u].val); root = merge(x,y);
} void nxt(int val) {
split(root,val,x,y); int u = y;
while(fhq[u].l) u = fhq[u].l; printf("%d\n",fhq[u].val); root = merge(x,y);
} int main() {
int n;
scanf("%d",&n);
insert(2147483647), insert(-2147483647); // 哨兵 while(n--) {
int opt,val;
scanf("%d%d",&opt,&val); if(opt == 1) insert(val);
if(opt == 2) del(val);
if(opt == 3) getrank(val);
if(opt == 4) getval(val);
if(opt == 5) pre(val);
if(opt == 6) nxt(val);
} return 0;
}

FHQ-Treap 简介的更多相关文章

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

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

  2. 浅谈fhq treap

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

  3. fhq treap 学习笔记

    序 今天心血来潮,来学习一下fhq treap(其实原因是本校有个OIer名叫fh,当然不是我) 简介 fhq treap 学名好像是"非旋转式treap及可持久化"...听上去怪 ...

  4. 简析平衡树(四)——FHQ Treap

    前言 好久没码过平衡树了! 这次在闪指导的指导下学会了\(FHQ\ Treap\),一方面是因为听说它可以可持久化,另一方面则是因为听说它是真的好写. 简介 \(FHQ\ Treap\),又称作非旋\ ...

  5. fhq treap最终模板

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

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

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

  7. 【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 ...

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

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

  9. 「FHQ Treap」学习笔记

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

  10. FHQ Treap摘要

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

随机推荐

  1. 镜头随人物而动,视频编辑服务让用户稳站C位

    现如今,视频是用户记录生活最热门的方式,各种App在发布视频界面都提供了视频简单剪辑的功能.除了增加音乐.滤镜.贴纸这些基础功能以外,用户越来越追求镜头感,这往往需要通过专业的视频剪辑软件手动打上关键 ...

  2. 一文看懂 ZooKeeper ,面试再也不用背八股(文末送PDF)

    ZooKeeper知识点总结 一.ZooKeeper 的工作机制 二.ZooKeeper 中的 ZAB 协议 三.数据模型与监听器 四.ZooKeeper 的选举机制和流程 本文将以如下内容为主线讲解 ...

  3. vue新手入门之使用vue框架搭建用户登录注册案例,手动搭建webpack+Vue项目(附源码,图文详解,亲测有效)

    前言 本篇随笔主要写了手动搭建一个webpack+Vue项目,掌握相关loader的安装与使用,包括css-loader.style-loader.vue-loader.url-loader.sass ...

  4. django框架9

    内容概要 用户名动态校验 删除二次确认 sweetalert前端插件 django自带的序列化组件 批量数据操作 分页器推导流程 自定义分页器封装代码 自定义分页器使用方法 校验性组件之forms组件 ...

  5. 软件成分分析(SCA)完全指南

    上一篇文章中,我们讨论了 DAST 的概念.重要性及其工作原理.那在开发过程中如何查找开源软件包中的漏洞并学习如何修复?本指南带你一起了解 SCA 工具及其最佳实践. 如今,绝大多数代码驱动的应用程序 ...

  6. 安装typescript环境并开启VSCode自动监视编译ts文件为js文件

    一.前言 小编最近开始学习typescript,懂得人都知道,typescript是vue3的基础伴生,配合更加默契.就像vue2和js一样!typescript不像js那样浏览器直接可以解读,需要我 ...

  7. element ui 自定义主题失败(primordials is not defined)

    卸载: 1.卸载cnpm npm uninstall cnpm -g 2.卸载vue-cli npm uninstall @vue/cli -g 3.卸载nodejs和删除文件 C:\Program ...

  8. SAP Web Dynpro-门户集成

    您可以将ABAP应用程序集成到企业门户中. 您还可以从Web Dynpro应用程序管理门户网站功能. 您可以调用Web Dynpro代码向导来访问门户网站管理器方法. 这可以用来执行以下功能- 门户网 ...

  9. ssh-基于ssh的文件传输

    scp 基于ssh做Linux主机间的文件传输     scp  文件路径  用户名@被传输的主机名/IP:文件要存放的路径     scp  /etc/fstab  root@10.0.0.2:/t ...

  10. 从区划边界geojson中查询经纬度坐标对应的省市区县乡镇名称,开源Java工具,内存占用低、高性能

    目录 坐标边界查询工具:AreaCity-Query-Geometry 性能测试数据 测试一:Init_StoreInWkbsFile 内存占用很低(性能受IO限制) 测试二:Init_StoreIn ...