简介

无旋树堆(一般统称 \(\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. .Net core--创建一个单元测试xUnit

    创建一个xUnit项目  webApi.test 创建之后会有一个默认的[Fact]  (测试的标准格式) [Fact] public void TestEqual() { int a = 10, b ...

  2. 谣言检测(RDCL)——《Towards Robust False Information Detection on Social Networks with Contrastive Learning》

    论文信息 论文标题:Towards Robust False Information Detection on Social Networks with Contrastive Learning论文作 ...

  3. MyBatis笔记03------XXXMapper.xml文件解析

    SQL映射文件的配置解析 当我们写好mapper(dao)层接口时,然后在对应的XXXMapper.xml文件中写业务逻辑对应的SQL映射语句,通过这个文件中可以实现CRU操作,那么下面说明如何编写这 ...

  4. 论文笔记 - SIMILAR: Submodular Information Measures Based Active Learning In Realistic Scenarios

    motivation Active Learning 存在的重要问题:现实数据极度不平衡,有许多类别很少见(rare),又有很多类别是冗余的(redundancy),又有些数据是 OOD 的(out- ...

  5. 系统整理K8S的配置管理实战-建议收藏系列

    目录 一.ConfigMap 1.1.创建 1.1.1.from-file 1.1.2.from-env-file 1.1.3.from-literal 1.1.4.基于yaml文件创建 1.2.Po ...

  6. .NET 7.0 重磅发布及资源汇总

    2022-11-8 .NET 7.0 作为微软的开源跨平台开发平台正式发布.微软在公告中表示.NET 7为您的应用程序带来了C# 11 / F# 7,.NET MAUI,ASP.NET Core/Bl ...

  7. 带你了解NLP的词嵌入

    摘要:今天带领大家学习自然语言处理中的词嵌入的内容. 本文分享自华为云社区<[MindSpore易点通]深度学习系列-词嵌入>,作者:Skytier. 1 特征表示 在自然语言处理中,有一 ...

  8. [CS61A] Lecture 4. Higher-Order Functions & Project 1: The Game of Hog

    [CS61A] Lecture 4. Higher-Order Functions & Project 1: The Game of Hog Lecture Lecture 4. Higher ...

  9. Dubbo-时间轮设计

    前言 Dubbo源码阅读分享系列文章,欢迎大家关注点赞 SPI实现部分 Dubbo-SPI机制 Dubbo-Adaptive实现原理 Dubbo-Activate实现原理 Dubbo SPI-Wrap ...

  10. RAID5的配置流程及模拟硬盘损坏

    个人名片: 对人间的热爱与歌颂,可抵岁月冗长 Github‍:念舒_C.ying CSDN主页️:念舒_C.ying 个人博客 :念舒_C.ying RAID5的配置流程及模拟硬盘损坏 1.在虚拟机中 ...