简介

无旋树堆(一般统称 \(\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. Windows Server 2012部署遇到的问题

    一.安装IIS提示"服务器管理器WinRM插件可能已损坏或丢失" 解决方案: 1.开启WinRM服务,添加ip监听 在服务中查看WinRM服务是否开启,如果没有开启则把该服务开启, ...

  2. Dropout原理分析

    工作流程 dropout用于解决过拟合,通过在每个batch中删除某些节点(cell)进行训练,从而提高模型训练的效果. 通过随机化一个伯努利分布,然后于输入y进行乘法,将对应位置的cell置零.然后 ...

  3. Day3:学习Java的第一步:Hello World!

    HelloWorld 新建一个文件夹存放代码 新建一个JAVA文件 文件后缀名为.java Hello.java 打开文件扩展名,即可查看到文件类型 运行文件:右键Hello.java文件用notep ...

  4. nginx安装及相关操作

    工作中经常用到nginx,今天写个自动部署nginx的脚本.nginx版本选用:1.20.2 1.创建nginx安装脚本(nginx.sh) [root@iZ2ze7uphtapcv51egcm7rZ ...

  5. 更改安装Oracle数据库时设定的System sys等用户的密码

    因本地Oracle数据库安装久远,不知道连接账号密码,查阅了一些资料最终修改成功,Mark up! 1 在开始菜单找到Oracle服务,打开SQL plus 2 输入命令连接到数据库并修改部分用户密码 ...

  6. 嵌入式-C语言基础:指针数组(和数组指针区分开来)

    指针数组:一个数组,若其元素均为指针类型的数据,称为指针数组,指针数组存放的是指针类型的数据,也就是指针数组的每个元素都存放一个地址.下面定义一个指针数组: int * p[4];//[]的优先级是比 ...

  7. 2022-11-03 Acwing每日一题

    本系列所有题目均为Acwing课的内容,发表博客既是为了学习总结,加深自己的印象,同时也是为了以后回过头来看时,不会感叹虚度光阴罢了,因此如果出现错误,欢迎大家能够指出错误,我会认真改正的.同时也希望 ...

  8. 2022春每日一题:Day 25

    题目:青蛙的约会 读完题,显然可以的到下同余方程:x+mk≡y+nk (mod L) 移项变成 (m-n)k+aL=y-x 只有k,L是未知的,而这题要求非负整数k的最小值,显然拓展欧几里得算法. 然 ...

  9. layui的table数据匹配问题

    <script> layui.use('table', function () { var table = layui.table; //第一个实例 table.render({ elem ...

  10. WebApi如何启用Session并且使用

    首先打开项目的Global.asax文件,重新方法init public override void Init() { //注册事件 this.AuthenticateRequest += WebAp ...