浅谈 FHQ Treap

浅谈 FHQ Treap

目录
  • 浅谈 FHQ Treap
    • 简单介绍
    • 前置操作
      • 结构
      • 分裂 split
      • 合并 merge
    • 一般操作
      • Insert
      • delete
      • 查询排名为 \(x\) 的数
      • 查询 \(v\) 的排名 rank
      • 查询 \(x\) 的前驱 precursor
      • 查询 \(x\) 的后继 successor
    • 版题

简单介绍

FHQ Treap,以下简写为 \(fhq\),是一种treap(树堆)的变体,功能比treap强大。
\(fhq\) 不需要通过一般平衡树的左右旋转来保持平衡,而是通过分裂 \(split\)和合并 \(merge\) 来实现操作。

优点是比较好理解、代码短、上手快、可持久化

主要是不用像 \(splay\) 一样旋来旋去。

Treap比较好玩的一点是,它整体是拥有二叉搜索树的性质,但是它的每一个节点都会有一个附加权值,它的附加权值是符合堆的性质。

这样我们可以明显看出,这棵树的形态(平衡与否)是由这个附加权值决定的。

那我们该如何取这个权值呢?随机!!

是的,我们每次都随机一个权值作为它的附加权值,那么它就大概是平衡的。

只需要两个基础操作,就可以达到splay的所有功能

前置操作

结构

对于每个树上节点开一个结构体,维护左儿子、右儿子、两个权值、还有子树中的节点个数。

然后给定一个树上的权值 \(v\)​ ,新建节点,还有更新子树大小 \(update\)

struct fhq {int l , r , v , t , sz;
} tr[N];
void update (int x) { tr[x].sz = 1 + tr[tr[x].l].sz + tr[tr[x].r].sz; }
int new_pos (int v) {tr[++pos].sz = 1;tr[pos].v = v;tr[pos].t = rnd ();return pos;
}

\(rnd()\) 是随机数生成函数,需要搞一个预处理

std::mt19937 rnd(233);

分裂 split

这个的主要思想就是把一个 \(treap\) 按照一个权值 \(v\) 分成两个。

把所有小于等于 \(v\) 的分到一棵树中,把所有大于 \(v\) 的分到另一棵树中,如下图。

3142227-20240519195920075-1936927548.png (1360×382) (cnblogs.com)

可以对照代码感性理解一下

void split (int now , int k , int &x , int &y) {if (now == 0) x = y = 0;else {if (tr[now].v <= k) x = now , split (tr[now].r , k , tr[now].r , y);else y = now , split (tr[now].l , k , x , tr[now].l);update (now);}
}

合并 merge

\(merge\) 操作按照堆权合并x,y两颗子树,合并前要保证x子树中的权值<y子树中的权值,如下图。

3142227-20240519201419216-492141250.png (1489×368) (cnblogs.com)

图中黄字为堆权

int merge (int x , int y) {if (x == 0 || y == 0) return x + y;if (tr[x].t < tr[y].t) {tr[x].r = merge (tr[x].r , y);update (x);return x;}else {tr[y].l = merge (x , tr[y].l);update (y);return y;}
}

一般操作

Insert

插入一个权值为 \(v\) 的点,把树按照 \(v\) \(split\) 成两个,再按照顺序合并。

split (rt , a , x , y);
rt = merge (merge (x , new_pos (a)) , y);

delete

删除权值为 \(v\) 的点,需要 \(split\) 两次。一次按照 \(v\) 分成 \(x , z\) ,一次把 \(x\) 按照 \(v - 1\) 分成 \(x , y\) ,那么 \(y\) 的中间点就是 \(v\) ,把 \(y\) 的两个儿子直接合并也就相当于删除了 \(v\) ,然后再按照顺序把其他节点合并就好了。

split (rt , a , x , z);
split (x , a - 1 , x , y);
y = merge (tr[y].l , tr[y].r);
rt = merge (merge (x , y) , z);

查询排名为 \(x\) 的数

就是普通的查询,每次把 \(x\) 和当前节点的左子树大小比较即可。

int kth (int now , int k) {while (1) {if (k <= tr[tr[now].l].sz) now = tr[now].l;else if (k == tr[tr[now].l].sz + 1) return now;else k -= tr[tr[now].l].sz + 1 , now = tr[now].r;}
}

查询 \(v\) 的排名 rank

把原树 \(split\) 按照 \(v - 1\)\(x , y\)\(x\) 的大小 \(+1\) 即为 \(x\) 的排名。

split (rt , a - 1 , x , y);
printf ("%d\n" , tr[x].sz + 1);
rt = merge (x , y);

查询 \(x\) 的前驱 precursor

找前驱的话把原树按 \(v-1\) \(split\)\(x,y\),在 \(x\) 里面找最大值。

split (rt , a - 1 , x , y);
printf ("%d\n" , tr[kth (x , tr[x].sz)].v);
rt = merge (x , y);

查询 \(x\) 的后继 successor

跟前驱类似。把原树按 \(v\) \(split\)\(x,y\),在 \(y\) 里找最小值。

split (rt , a , x , y);
printf ("%d\n" , tr[kth (y , 1)].v);
rt = merge (x , y);

版题

题目传送门

#include <bits/stdc++.h>
#define fu(x , y , z) for(int x = y ; x <= z ; x ++)
using namespace std;
const int N = 5e5 + 5;
std::mt19937 rnd(233);
int pos , rt;
struct fhq {int l , r , v , t , sz;
} tr[N];
void update (int x) { tr[x].sz = 1 + tr[tr[x].l].sz + tr[tr[x].r].sz; }
int new_pos (int v) {tr[++pos].sz = 1;tr[pos].v = v;tr[pos].t = rnd ();return pos;
}
void split (int now , int k , int &x , int &y) {if (now == 0) x = y = 0;else {if (tr[now].v <= k) x = now , split (tr[now].r , k , tr[now].r , y);else y = now , split (tr[now].l , k , x , tr[now].l);update (now);}
}
int merge (int x , int y) {if (x == 0 || y == 0) return x + y;if (tr[x].t < tr[y].t) {tr[x].r = merge (tr[x].r , y);update (x);return x;}else {tr[y].l = merge (x , tr[y].l);update (y);return y;}
}
int kth (int now , int k) {while (1) {if (k <= tr[tr[now].l].sz) now = tr[now].l;else if (k == tr[tr[now].l].sz + 1) return now;else k -= tr[tr[now].l].sz + 1 , now = tr[now].r;}
}
int main () {int T , op , a , x , y , z;scanf ("%d" , &T);while (T --) {scanf ("%d%d" , &op , &a);if (op == 1) {split (rt , a , x , y);rt = merge (merge (x , new_pos (a)) , y);}else if (op == 2) {split (rt , a , x , z);split (x , a - 1 , x , y);y = merge (tr[y].l , tr[y].r);rt = merge (merge (x , y) , z);}else if (op == 3) {split (rt , a - 1 , x , y);printf ("%d\n" , tr[x].sz + 1);rt = merge (x , y);} else if (op == 4) printf ("%d\n" , tr[kth (rt , a)].v);else if (op == 5) {split (rt , a - 1 , x , y);printf ("%d\n" , tr[kth (x , tr[x].sz)].v);rt = merge (x , y);}  else {split (rt , a , x , y);printf ("%d\n" , tr[kth (y , 1)].v);rt = merge (x , y);}}return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/708907.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

INFINI Labs 产品更新 | Easysearch 1.8.0 发布数据写入限流功能

INFINI Labs 产品又更新啦~,包括 Easysearch v1.8.0、Gateway、Console、Agent、Loadgen v1.25.0。本次各产品更新了很多亮点功能,如 Easysearch 新增数据写入限流功能,可实现节点、分片级限流;Gateway 修复数据迁移过程中因消费不及时解压缩导致部分数据记录损坏而丢失记录…

OpenStack Centos7 T版本搭建

目录Centos7搭建OpenStack T版本 --上1. 环境准备(所有节点操作)1.1 修改主机名1.2 关闭selinux 以及防火墙1.3 修改hosts1.4 配置时间同步controller 操作compute以及其他节点操作1.5 配置OpenStack 软件包1.6 安装数据库1.7 安装消息队列1.8 安装memcached1.9 安装etcd2. 安装…

数据库开发基础(JDBC)

时间:2024-05-17日,星期五 数据库开发基础(Java) 课程内容 JDBC快速入门、使用JDBC开发细节、连接池与JDBC进阶使用JDBC使用步骤数据库查询方法数据库写入方法SQL注入攻击的应对连接池的使用Apache Commons DBUtilsJDBC快速入门 JDBC是Java中为了去方便使用各种数据而创造出…

视野修炼-技术周刊第84期 | Mako

① npmmirror 下线 unpkg 服务 ② Mako - 又一基于 Rust 的构建工具 ③ Icnes - 图标集合检索 ④ go-masonry-gallery - 图片瀑布流页面生成 ⑤ Color Pick - 在线从图片中提取颜色的 ⑥ 制作 CSS 形状的现代指南 ⑦ 针对前端初学者的项目合集 ⑧ Jan - 本地运行大模型客户端欢…

jQuery2-动画技术入门指南-全-

jQuery2 动画技术入门指南(全)原文:zh.annas-archive.org/md5/71BE345FA56C4A075E859338F3DCA6DA 译者:飞龙 协议:CC BY-NC-SA 4.0序言 jQuery 是一个跨浏览器的 JavaScript 库,旨在简化 HTML 的客户端脚本编写,并且是当今最流行的 JavaScript 库。利用 jQuery 提供的功…

EDP .Net开发框架--业务模型

EDP是一套集组织架构,权限框架【功能权限,操作权限,数据访问权限,WebApi权限】,自动化日志,动态Interface,WebApi管理等基础功能于一体的,基于.net的企业应用开发框架。通过友好的编码方式实现数据行、列权限的管控。平台下载地址:https://gitee.com/alwaysinsist/edp…

CentOS挂载硬盘

1.查看新添加的硬盘 fdisk -l2.挂载到/data目录 mount /dev/vdb1 /data 3.设置开机自启 vi /etc/fstab 在最后一行添加 /dev/vdb1 /data ext4 defaults 0 0

sed编辑器和awk

目录1.sed的执行过程(1)sed 的工作流程(2)打印内容(3)删除(4)替换(5)打印被修改的行(6)插入(7)复制粘贴2.awk(1)工作原理 1.sed的执行过程 sed是一种流编辑器,流编辑器会在编辑器处理数据之前基于预先提供的一组规则来编辑数据流。 sed编辑器可以根据命令来处…

“复兴杯”2023第四届大学生网络安全精英赛排位赛 Writeup

时间跟全国信安初赛重了(),不过也是第一次在CTF AK了( 个人信息 个人排名:15 解题过程 1观察代码,使用科学技术法进行绕过,2.023e3也就是2.023*10^3=2023,弱比较时会化为2023,但是运算时后并不绝对等于2024。 输入得到flag。2 打开网站可以看到电脑账号是ly,使用过滤…

基于webapi的websocket聊天室(四)

上一篇实现了多聊天室。这一片要继续改进的是实现收发文件,以及图片显示。 效果问题 websocket本身就是二进制传输。文件刚好也是二进制存储的。 文件本身的传输问题不太,但是需要传输文件元数据,比如文件名和扩展名之类的。这很必要,如果我们想知道怎么展示这个文件的话。比…

嵌入式Linux中的LED驱动控制(以野火STM32MP157开发板为例)

在嵌入式Linux系统中,由于从硬件到软件都是自己定制的,所以很多时候需要对自己定义的设备编写驱动程序。本例就以野火STM32MP157开发板为例,讨论如何控制开发板上三个LED的亮灭。 先来看一下LED部分的电路原理图,如下所示。从上图中可以看到,三个RGB颜色的二极管采用共阳接…

BUUctf xor

0x01 关于xor xor,即为计算机中的异或计算,相同为0,不同为1。 下面是关于异或加密的四个定理A ^ 0 = A A ^ A = 0 (A ^ B) ^ C = A ^ (B ^ C) (B ^ A) ^ A = B ^ 0 = B // 明文 B;密码 A观察可知,经历异或加密后的密文,再次进行异或算法即可得到明文。 0x02 题解 先丢进…