简介

无旋树堆(一般统称 \(\text{FHQ-Treap}\)),是一种平衡树。可以用很少的代码达到很优秀的复杂度。

前置知识:

  • 二叉搜索树 \(\text{BST}\)
  • \(\text{Treap}\) 基本知识

普通平衡树

例题引入

P6136 【模板】普通平衡树(数据加强版)

您需要写一种数据结构,来维护一些整数,其中需要提供以下操作:

  1. 插入一个整数 \(x\)。
  2. 删除一个整数 \(x\)(若有多个相同的数,只删除一个)。
  3. 查询整数 \(x\) 的排名(排名定义为比当前数小的数的个数 \(+1\))。
  4. 查询排名为 \(x\) 的数(如果不存在,则认为是排名小于 \(x\) 的最大数。保证 \(x\) 不会超过当前数据结构中数的总数)。
  5. 求 \(x\) 的前驱(前驱定义为小于 \(x\),且最大的数)。
  6. 求 \(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();
}

AC Record

普通平衡树例题

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)学习笔记的更多相关文章

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

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

  2. fhq treap 学习笔记

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

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

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

  4. treap学习笔记

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

  5. fhq treap抄袭笔记

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

  6. 树堆(Treap)学习笔记 2020.8.12

    如果一棵二叉排序树的节点插入的顺序是随机的,那么这样建立的二叉排序树在大多数情况下是平衡的,可以证明,其高度期望值为 \(O( \log_2 n )\).即使存在一些极端情况,但是这种情况发生的概率很 ...

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

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

  8. 树堆(Treap)

    平衡树 简介: 平衡二叉树(Balanced Binary Tree)具有以下性质:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树.平衡二叉树的常用实现方 ...

  9. [Treap][学习笔记]

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

  10. 矩阵树定理(Matrix Tree)学习笔记

    如果不谈证明,稍微有点线代基础的人都可以在两分钟内学完所有相关内容.. 行列式随便找本线代书看一下基本性质就好了. 学习资源: https://www.cnblogs.com/candy99/p/64 ...

随机推荐

  1. 【做题笔记】CSP-S 往年试题

    题单 本文章正在持续更新-- [2021] 廊桥分配 题目 题面描述 所有飞机分为两类--国内区和国际区,两区廊桥数量互不干扰.每架飞机遵循"先到先得"的原则,优先选择编号最小的廊 ...

  2. 7_vue的数据代理,双向绑定

    回顾 object.defineProperty() 方法 区别 defineProperty == 给对象定义属性用的 需要传递三个基本参数 需要定义属性的对象名 你要定义的属性叫什么名字(比如给p ...

  3. JS中对DOM元素的操作

    https://www.runoob.com/jquery/jquery-ref-html.html 1.each  遍历 //遍历所有class为checksingle的DOM元素 $(" ...

  4. 2022UUCTF--WEB

    websign 无法右键 禁用js后 看源码 ez_rce -- 闭合 源码,禁用的东西挺多的 仔细发现 ? <> `没有禁用,闭合标签反引号执行命令 ## 放弃把,小伙子,你真的不会RC ...

  5. 表驱动法在STM32中的应用

    1.概念 所谓表驱动法(Table-Driven Approach)简而言之就是用查表的方法获取数据.此处的"表"通常为数组,但可视为数据库的一种体现.根据字典中的部首检字表查找读 ...

  6. 部署grafana+telegraf+influxdb 及 配置 jmeter后端监听

    搞性能测试,可以搭建Grafana+Telegraf+InfluxDB 监控平台,监控服务器资源使用率.jmeter性能测试结果等. telegraf: 是一个用 Go 编写的代理程序,可收集系统和服 ...

  7. Jenkinsfile Pipeline 使用 SSH 连接

    前提 首先你需要将用到的 SSH 私钥保存到 Jenkins 的凭据中,这样你会获得一个 credentialId.这不是本文主要的内容,故不在此展开赘述,详情可参考官方文档:https://www. ...

  8. Window10开机键盘映射

    一.映射工具 1.github地址 https://github.com/susam/uncap 2.映射方式 (1)CapsLock映射成ESC键 uncap 0x1b:0x14 (2)CapsLo ...

  9. 链表实现-回文palindrome判断

    1.数字回文判断(逆转,分离未位,砍掉个位,保存原来) s = s * 10 + a%10 a = a/10 2.字符串判断回文 package main //思路: 开发一个栈来来存放链表的上半段f ...

  10. C温故补缺(二):volatile

    volatile 参考:CSDN volatile也是一个类型修饰符,被其修饰的变量意味着可以被某些编译器未知的因素修改,如操作系统,硬件,线程等. 当遇到volatile修饰的变量时,编译器对访问该 ...