学习笔记:fhq-treap
0. 前置知识:\(treap\)的定义
树堆,在数据结构中也称Treap,是指有一个随机附加域满足堆的性质的二叉搜索树,其结构相当于以随机数据插入的二叉搜索树。
>——摘自百度百科
形象化一点:
- \(treap\)是关于\(val\)的二叉搜索树
- \(treap\)是关于\((随机权值)rdm(随机权值)\)的二叉搜索树
为了满足上面两条性质,就要分情况对\(treap\)进行旋转
怎么转我也不会……因为这是介绍\(fhq\) \(treap\)的文章
1.\(fhq\) \(treap\)
基本定义&操作
解释下数组含义
int son[N][2]; //0:左儿子 1:右儿子
int sz[N];//子树大小
int val[N];//权值
int rdm[N];//随机权值
int cnt;//总treap的节点个数
如何开一个新节点
int new_node(int x) {
val[++cnt]=x;//节点权值
rdm[cnt]=rand();//随机权值
sz[cnt]=1;//子树大小
return cnt;
}
\(pushup\)
inline void pushup(int x) {
sz[x]=sz[son[x][0]]+sz[son[x][1]]+1;
}
核心操作
\(fhq\) \(treap\)的核心操作只有两种:\(split\)和\(merge\),这两种操作巧妙玄学地让每次插入和删除,\(treap\)的性质都不会被破坏,自然也就不用旋转
\(merge\)
把a、b两棵\(treap\)合成一棵大\(treap\)的操作
满足a所有节点的权值 < b所有节点的权值
这样就只用维护随机权值的\(heap\)性质
代码:
void merge(int &now,int x,int y) { //分别为根节点地址,a,b
if(!x || !y) {
now=x+y;
return;
}
if(rdm[x]<rdm[y]) { //维护rdm堆
now=x;//保留a的左子树
merge(son[now][1],son[now][1],y);//把b塞进a的右子树,维护bst
}else { //原理同上
now=y;
merge(son[now][0],x,son[now][0]);
}
pushup(now);
}
\(split\)
把一棵大\(treap\)按照某种划分标准分成\(a\)、\(b\)两棵\(treap\)的操作
常用的划分标准有权值\(val\)和子树大小\(sz\),两种代码都会给
代码:
- 按val划分
//划分后,a中所有元素<=k,b中所有元素>k
void split(int now,int k,int &x,int &y) {//分别为:当前节点,划分标准,a的树根,b的树根
if(!now) {//没东西了
x=y=0;
return;
}
if(val[now]<=k) {//利用bst的性质
x=now;
split(son[now][1],k,son[now][1],y);
}else {
y=now;
split(son[now][0],k,x,son[now][0]);
}
pushup(now);
}
- 按sz划分
//把大treap中前k小的元素放到a中,剩下放到b中
void split(int now,int k,int &x,int &y) {
if(!now) {//没东西了
x=y=0;
return;
}
if(sz[son[now][0]]>k) {
x=now;
split(son[now][1],k-sz[son[now][0]]-1,son[now][1],y);
}else {
y=now;
split(son[now][0],k,x,son[now][0]);
}
pushup(now);
}
2.模板题
洛谷 3369/BZOJ 3224/Tyvj 1728 普通平衡树
#include <bits/stdc++.h>
#define N 100005
using namespace std;
int root=0;
struct fhq_treap {
int son[N][2],sz[N],val[N],rdm[N],cnt;
inline int new_node(int x) {
val[++cnt]=x,rdm[cnt]=rand(),sz[cnt]=1;
return cnt;
}
inline void pushup(int x) {
sz[x]=sz[son[x][0]]+sz[son[x][1]]+1;
}
void split(int now,int k,int &x,int &y) {
if(!now) {
x=y=0;
return;
}
if(val[now]<=k) {
x=now;
split(son[now][1],k,son[now][1],y);
}else {
y=now;
split(son[now][0],k,x,son[now][0]);
}
pushup(now);
}
void merge(int &now,int x,int y) {
if(!x || !y) {
now=x+y;
return;
}
if(rdm[x]<rdm[y]) {
now=x;
merge(son[now][1],son[now][1],y);
}else {
now=y;
merge(son[now][0],x,son[now][0]);
}
pushup(now);
}
void kth(int now,int k) {//第k大数
while(1) {
if(sz[son[now][0]]>=k) {
now=son[now][0];
}else {
if(sz[son[now][0]]+1==k) break;
k-=(sz[son[now][0]]+1);
now=son[now][1];
}
}
printf("%d\n",val[now]);
}
}T;
int main() {
int n,op,x,a,b,c;
scanf("%d",&n);
T.sz[0]=0;
T.cnt=0;
while(n--) {
a=0,b=0;
scanf("%d%d",&op,&x);
if(op==1) { //插入x数
T.split(root,x,a,b);
int t=T.new_node(x);
T.merge(a,a,t);
T.merge(root,a,b);
}
if(op==2) { //删除x数
T.split(root,x,a,b);
T.split(a,x-1,a,c);
T.merge(c,T.son[c][0],T.son[c][1]);
T.merge(a,a,c);
T.merge(root,a,b);
}
if(op==3) { //查询x数的排名
T.split(root,x-1,a,b);
printf("%d\n",T.sz[a]+1);
T.merge(root,a,b);
}
if(op==4) { //查询排名为x的数
T.kth(root,x);
}
if(op==5) { //求x的前驱
T.split(root,x-1,a,b);
T.kth(a,T.sz[a]);
T.merge(root,a,b);
}
if(op==6) { //求x的后继
T.split(root,x,a,b);
T.kth(b,1);
T.merge(root,a,b);
}
}
}
洛谷 3391 文艺平衡树
这题只有区间翻转操作,所以不用考虑那么多有的没的.
可以把每个点的编号看作权值。假设要翻转区间\([l,r]\),先把\([l,r]\)这个区间分裂出来,打个标记,下传维护时直接\(swap(lson,rson)\)就行了
然而这样会破坏\(treap\)的性质,所以还有区间\(k\)大的话不能直接查
能写\(splay\)尽量写\(splay\)吧,\(fhq\) \(treap\)常数有点大……
#include <bits/stdc++.h>
#define N 100010
using namespace std;
int root=0;
struct fhq_treap{
#define lson son[now][0]
#define rson son[now][1]
int rdm[N],val[N],w[N],sz[N],son[N][2],cnt=0;
bool tag[N];//翻转的lazytag
int NewNode(){
val[++cnt]=cnt,rdm[cnt]=rand(),sz[cnt]=1,tag[cnt]=0;
return cnt;
}
void pushup(int now){sz[now]=sz[lson]+sz[rson]+1; }
void pushdown(int now){
swap(lson,rson);
if(lson) tag[lson]^=1;
if(rson) tag[rson]^=1;
tag[now]=0;
}
void merge(int &now,int x,int y){
if(!x || !y){
now=x+y;
return;
}
if(rdm[x]<rdm[y]){
if(tag[x]) pushdown(x);
now=x,merge(rson,rson,y);
}
else{
if(tag[y]) pushdown(y);
now=y,merge(lson,x,lson);
}
pushup(now);
}
void split(int now,int k,int &x,int &y){
if(!now){
x=y=0;
return;
}
if(tag[now]) pushdown(now);
if(sz[lson]+1<=k) x=now,split(rson,k-sz[lson]-1,rson,y);
else y=now,split(lson,k,x,lson);
pushup(now);
}
void print(int now){//中序遍历输出答案
if(!now) return;
if(tag[now]) pushdown(now);
print(lson);
printf("%d ",val[now]);
print(rson);
}
}T;
int main(){
int n,m,l,r,i;
int a=0,b=0,c=0;
scanf("%d",&n);
for(i=1;i<=n;++i){
int x=T.NewNode();
T.merge(root,root,x);
}
scanf("%d",&m);
while(m--){
a=0,b=0,c=0;
scanf("%d%d",&l,&r);
T.split(root,l-1,a,b);
T.split(b,r-l+1,b,c);
T.tag[b]^=1;
T.merge(b,b,c),T.merge(root,a,b);
}
T.print(root);
}
学习笔记:fhq-treap的更多相关文章
- 「学习笔记」Treap
「学习笔记」Treap 前言 什么是 Treap ? 二叉搜索树 (Binary Search Tree/Binary Sort Tree/BST) 基础定义 查找元素 插入元素 删除元素 查找后继 ...
- [学习笔记] 平衡树——Treap
前置技能:平衡树前传:BST 终于学到我们喜闻乐见的平衡树啦! 所以我们这次讲的是平衡树中比较好写的\(Treap\). (以后会写splay的先埋个坑在这) 好了,进入正题. step 1 我们知道 ...
- fhq treap 学习笔记
序 今天心血来潮,来学习一下fhq treap(其实原因是本校有个OIer名叫fh,当然不是我) 简介 fhq treap 学名好像是"非旋转式treap及可持久化"...听上去怪 ...
- fhq treap最终模板
新学习了fhq treap,厉害了 先贴个神犇的版, from memphis /* Treap[Merge,Split] by Memphis */ #include<cstdio> # ...
- FHQ treap学习(复习)笔记
.....好吧....最后一篇学习笔记的flag它倒了..... 好吧,这篇笔记也鸽了好久好久了... 比赛前刷模板,才想着还是补个坑吧... FHQ,这个神仙(范浩强大佬),发明了这个神仙的数据结构 ...
- 「FHQ Treap」学习笔记
话说天下大事,就像fhq treap —— 分久必合,合久必分 简单讲一讲.非旋treap主要依靠分裂和合并来实现操作.(递归,不维护fa不维护cnt) 合并的前提是两棵树的权值满足一边的最大的比另一 ...
- 「学习笔记」 FHQ Treap
FHQ Treap FHQ Treap (%%%发明者范浩强年年NOI金牌)是一种神奇的数据结构,也叫非旋Treap,它不像Treap zig zag搞不清楚(所以叫非旋嘛),也不像Splay完全看不 ...
- Fhq Treap [FhqTreap 学习笔记]
众所周知 Fhq Treap 是 fhq 神仙研究出来的平衡树- 具体实现 每个点实现一个 \(\text{rnd}\) 表示 rand 的值 为什么要 rand 呢 是为了保证树高为 \(\log ...
- 平衡树学习笔记(2)-------Treap
Treap 上一篇:平衡树学习笔记(1)-------简介 Treap是一个玄学的平衡树 为什么说它玄学呢? 还记得上一节说过每个平衡树都有自己的平衡方式吗? 没错,它平衡的方式是......rand ...
- 左偏树 / 非旋转treap学习笔记
背景 非旋转treap真的好久没有用过了... 左偏树由于之前学的时候没有写学习笔记, 学得也并不牢固. 所以打算写这么一篇学习笔记, 讲讲左偏树和非旋转treap. 左偏树 定义 左偏树(Lefti ...
随机推荐
- JEECG&JWT异常捕获强化处理 | Java: Meaning of catch (final SomeException e)?
//从header中得到token String authHeader = request.getHeader(JwtConstants.AUTHORIZATION); if (authHeader ...
- IDEA将项目上传至码云/GitHub托管
怎么将本地的项目放到码云或者GitHub去托管了?(以码云为例) 一.创建远程项目 第一步:点击创建项目 第二步:填写项目相关信息 第三步:复制远程的项目地址,注意:此处码云官方已经给出上传项目方法, ...
- [转帖]Windows平台卸载Oracle的办法
1.首先打开服务:选中此电脑->点击右键->选择管理->选择服务和应用程序->服务 在右边查看并停止以 oracle开头的服务(选中正在运行的以oracle开头的服务-> ...
- [转帖]firewall-cmd
firewall-cmd https://wangchujiang.com/linux-command/c/firewall-cmd.html 高手大作 等哪天需要防火墙了 再练习一下. Linux上 ...
- 第六周作业----PSP&工作量
1. PSP 日期 类别 工作 开始时间 中断时间 结束时间 总时间 4.7 站立会议 "耐撕"团队站立会议 20:00 20:15 15 重构 重构"抢答器&q ...
- 索引使用,分析初探。(explain分析执行计划,以及强制使用force index)
促使这次探索的初衷还是因为要对一个定时脚本性能进行优化. 脚本有两个指定状态分别是status, latest_process_status,和一个超期时间expire_time进行限制. 按照我以前 ...
- css进行网站布局
一.一列布局(例如百度首页) 通常用 {margin:0 auto;} 控制. <!DOCTYPE> <html xmlns="http://www.w3.org/1999 ...
- mvc 学前必知
MVC无人不知,可很多程序员对MVC的概念的理解似乎有误,换言之他们一直在错用MVC,尽管即使如此软件也能被写出来,然而软件内部代码的组织方式却是不科学的,这会影响到软件的可维护性.可移植性,代码的可 ...
- Spring Boot 构建电商基础秒杀项目 (二) 使用 Spring MVC 方式获取用户信息
SpringBoot构建电商基础秒杀项目 学习笔记 修改 DOMapper 在 UserPasswordDOMapper.xml 添加: <select id="selectByUse ...
- WebAPI MVC Change Identity Default Table
看过之前的文章小伙伴们应该已经明白了,当我们新建一个带有身份验证的模板时,会自带Identity Server,并且它的表名和字段名也都是默认的. 那么该如何修改它,并让EF知道呢?不废话,直接上代码 ...