草庐IT

无旋树堆(FHQ-Treap)学习笔记

Xie Zheyuan 2023-03-28 原文

简介

无旋树堆(一般统称 \(\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. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  2. CAN协议的学习与理解 - 2

    最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总

  3. 深度学习部署:Windows安装pycocotools报错解决方法 - 2

    深度学习部署:Windows安装pycocotools报错解决方法1.pycocotools库的简介2.pycocotools安装的坑3.解决办法更多Ai资讯:公主号AiCharm本系列是作者在跑一些深度学习实例时,遇到的各种各样的问题及解决办法,希望能够帮助到大家。ERROR:Commanderroredoutwithexitstatus1:'D:\Anaconda3\python.exe'-u-c'importsys,setuptools,tokenize;sys.argv[0]='"'"'C:\\Users\\46653\\AppData\\Local\\Temp\\pip-instal

  4. ruby - 我正在学习编程并选择了 Ruby。我应该升级到 Ruby 1.9 吗? - 2

    我完全不是程序员,正在学习使用Ruby和Rails框架进行编程。我目前正在使用Ruby1.8.7和Rails3.0.3,但我想知道我是否应该升级到Ruby1.9,因为我真的没有任何升级的“遗留”成本。缺点是什么?我是否会遇到与普通gem的兼容性问题,或者甚至其他我不太了解甚至无法预料的问题? 最佳答案 你应该升级。不要坚持从1.8.7开始。如果您发现不支持1.9.2的gem,请避免使用它们(因为它们很可能不被维护)。如果您对gem是否兼容1.9.2有任何疑问,您可以在以下位置查看:http://www.railsplugins.or

  5. ruby - 我如何学习 ruby​​ 的正则表达式? - 2

    如何学习ruby​​的正则表达式?(对于假人) 最佳答案 http://www.rubular.com/在Ruby中使用正则表达式时是一个很棒的工具,因为它可以立即将结果可视化。 关于ruby-我如何学习ruby​​的正则表达式?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/1881231/

  6. 深度学习12. CNN经典网络 VGG16 - 2

    深度学习12.CNN经典网络VGG16一、简介1.VGG来源2.VGG分类3.不同模型的参数数量4.3x3卷积核的好处5.关于学习率调度6.批归一化二、VGG16层分析1.层划分2.参数展开过程图解3.参数传递示例4.VGG16各层参数数量三、代码分析1.VGG16模型定义2.训练3.测试一、简介1.VGG来源VGG(VisualGeometryGroup)是一个视觉几何组在2014年提出的深度卷积神经网络架构。VGG在2014年ImageNet图像分类竞赛亚军,定位竞赛冠军;VGG网络采用连续的小卷积核(3x3)和池化层构建深度神经网络,网络深度可以达到16层或19层,其中VGG16和VGG

  7. 机器学习——时间序列ARIMA模型(四):自相关函数ACF和偏自相关函数PACF用于判断ARIMA模型中p、q参数取值 - 2

    文章目录1、自相关函数ACF2、偏自相关函数PACF3、ARIMA(p,d,q)的阶数判断4、代码实现1、引入所需依赖2、数据读取与处理3、一阶差分与绘图4、ACF5、PACF1、自相关函数ACF自相关函数反映了同一序列在不同时序的取值之间的相关性。公式:ACF(k)=ρk=Cov(yt,yt−k)Var(yt)ACF(k)=\rho_{k}=\frac{Cov(y_{t},y_{t-k})}{Var(y_{t})}ACF(k)=ρk​=Var(yt​)Cov(yt​,yt−k​)​其中分子用于求协方差矩阵,分母用于计算样本方差。求出的ACF值为[-1,1]。但对于一个平稳的AR模型,求出其滞

  8. Unity Shader 学习笔记(5)Shader变体、Shader属性定义技巧、自定义材质面板 - 2

    写在之前Shader变体、Shader属性定义技巧、自定义材质面板,这三个知识点任何一个单拿出来都是一套知识体系,不能一概而论,本文章目的在于将学习和实际工作中遇见的问题进行总结,类似于网络笔记之用,方便后续回顾查看,如有以偏概全、不祥不尽之处,还望海涵。1、Shader变体先看一段代码......Properties{ [KeywordEnum(on,off)]USL_USE_COL("IsUseColorMixTex?",int)=0 [Toggle(IS_RED_ON)]_IsRed("IsRed?",int)=0}......//中间省略,后续会有完整代码 #pragmamulti_c

  9. Tcl脚本入门笔记详解(一) - 2

    TCL脚本语言简介•TCL(ToolCommandLanguage)是一种解释执行的脚本语言(ScriptingLanguage),它提供了通用的编程能力:支持变量、过程和控制结构;同时TCL还拥有一个功能强大的固有的核心命令集。TCL经常被用于快速原型开发,脚本编程,GUI和测试等方面。•实际上包含了两个部分:一个语言和一个库。首先,Tcl是一种简单的脚本语言,主要使用于发布命令给一些互交程序如文本编辑器、调试器和shell。由于TCL的解释器是用C\C++语言的过程库实现的,因此在某种意义上我们又可以把TCL看作C库,这个库中有丰富的用于扩展TCL命令的C\C++过程和函数,所以,Tcl是

  10. ruby-on-rails - 这个 C 和 PHP 程序员如何学习 Ruby 和 Rails? - 2

    按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visitthehelpcenter指导。关闭9年前。我来自C、php和bash背景,很容易学习,因为它们都有相同的C结构,我可以将其与我已经知道的联系起来。然后2年前我学了Python并且学得很好,Python对我来说比Ruby更容易学。然后从去年开始,我一直在尝试学习Ruby,然后是Rails,我承认,直到现在我还是学不会,讽刺的是那些打着简单易学的烙印,但是对于我这样一个老练的程序员来说,我只是无法将它

随机推荐