P3369 【模板】普通平衡树(FHQ Treap树构建和解析)

news2025/1/26 15:44:29

题目描述

您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:

  1. 插入 x 数
  2. 删除 x 数(若有多个相同的数,应只删除一个)
  3. 查询 x 数的排名(排名定义为比当前数小的数的个数 +1 )
  4. 查询排名为 x 的数
  5. 求 x 的前驱(前驱定义为小于 x,且最大的数)
  6. 求 x 的后继(后继定义为大于 x,且最小的数)

输入格式

第一行为 n,表示操作的个数,下面 n 行每行有两个数 opt 和 x,opt 表示操作的序号( 1≤opt≤6 )

输出格式

对于操作 3,4,5,6 每行输出一个数,表示对应答案

输入输出样例

输入 #1复制

10
1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598

输出 #1复制

106465
84185
492737

说明/提示

【数据范围】
对于 100% 的数据,1≤n≤105,∣x∣≤107

构建树:

对FHQTreap树进行构建:

FHQ Treap树是最后形态由键值和优先级决定。它的高明之处是所有操作都只用到了分裂和合并这两个基本操作,这两个操作的复杂度都为O(log2 n)。

前置知识:

  • C++
  • 二叉搜索树的基本性质,下面会讲
  • 二叉堆

二叉树搜索树是左节点小于根节点,根节点小于右节点。

用中序遍历可以看到是一个单调递增的序列。

二叉堆:

堆有大根堆和小根堆之分。

大根堆是根节点>左节点and 根节点>右节点;小根堆相反;

大根堆和小根堆一般上一层是满二叉树。这时会提高搜索的效率,时间复杂度减少。O(log2n)

有了以上的知识点:

对题目进行分析:

1、二叉树需要有左右节点。

2、要有一个键值,随机变量。

3、左右节点+本身的大小(这时在排名上有很大的用处)

代码如下:

struct Node {
	int ls, rs;   // 左右 子节点 
	int key, pri; //  key为值 pri 为随机的优先级
	int size; // 当前节点为根的子树的节点数量,用于求第k大和排名
}t[M]; 

核心:

1.分裂,返回以L和R为根的两棵树

先看图:划分为两棵树,左子树小于x,右子树大于x

 上代码:

void Split(int u, int x, int& L, int& R) {
	if (u == 0) { //到达叶子,递归返回
		L = 0, R = 0;
		return;
	}

	if (t[u].key <= x) { // 本节点比x小,那么到右子树找x 
		L = u;           // 左树的根是本节点  // 下一个如果到 这来 上一个的rs 为 这个节点u ,因为u.rs的全部字节点都大于 u的值
		Split(t[u].rs, x, t[u].rs, R); // 通过rs传回新的子节点  
	}
	else {
		R = u; // 根节点 
		Split(t[u].ls, x, L, t[u].ls);
	}
	Update(u);
}

返回两棵树的树根L和R。

2.合并两颗子树

这是分裂的逆过程;用随机值进行可以确保树的高度比较小,这就是为什么要引入随机值的原因。

先以小根堆为例,如图所示合并两棵树。

 我这里用了大根堆为例:

int Merge(int L, int R) {
	if (L == 0 || R == 0) {
		return L + R; // 左 or  右节点 
	} // 建立大顶堆
	if (t[L].pri > t[R].pri) { // 合并树  随机值 到的在右边
		t[L].rs = Merge(t[L].rs, R); // 
		Update(L);
		return L;
	}
	else {
		t[R].ls = Merge(L, t[R].ls);
		Update(R);
		return R;
	}
}

权值大的在上面。

有着两种思想就好写多了。

代码解释请看注释

解题代码:

#include<iostream>
#include<cmath>
#include<stdlib.h>
using namespace std;
const int M = 1e6 + 10;
int cnt = 0, root = 0;
struct Node {
	int ls, rs;   // 左右 子节点 
	int key, pri; //  key为值 pri 为随机的优先级
	int size; // 当前节点为根的子树的节点数量,用于求第k大和排名
}t[M]; 
void newNode(int x) {
	cnt++;
	t[cnt].size = 1;
	t[cnt].ls = t[cnt].rs = 0;
	t[cnt].key = x;
	t[cnt].pri = rand();
}
void Update(int u) {
	t[u].size = t[t[u].ls].size + t[t[u].rs].size+1;
}
void Split(int u, int x, int& L, int& R) {
	if (u == 0) { //到达叶子,递归返回
		L = 0, R = 0;
		return;
	}

	if (t[u].key <= x) { // 本节点比x小,那么到右子树找x 
		L = u;           // 左树的根是本节点  // 下一个如果到 这来 上一个的rs 为 这个节点u ,因为u.rs的全部字节点都大于 u的值
		Split(t[u].rs, x, t[u].rs, R); // 通过rs传回新的子节点  
	}
	else {
		R = u; // 根节点 
		Split(t[u].ls, x, L, t[u].ls);
	}
	Update(u);
}

int Merge(int L, int R) {
	if (L == 0 || R == 0) {
		return L + R; // 左 or  右节点 
	} // 建立大顶堆
	if (t[L].pri > t[R].pri) { // 合并树  随机值 到的在右边
		t[L].rs = Merge(t[L].rs, R); // 
		Update(L);
		return L;
	}
	else {
		t[R].ls = Merge(L, t[R].ls);
		Update(R);
		return R;
	}
}

void Insert(int x) {
	int L, R; // 左右根的节点
	Split(root, x, L, R); 
	newNode(x); //生成x 
	int aa = Merge(L, cnt); //合并节点,这里是生成的节点,合并左子树中
	root = Merge(aa, R); //两棵树进行合并
}
void Del(int x) {//删除节点
	int L, R, p;
	Split(root, x, L, R); //先抛出 左根的节点 小于等于x   右根  大于 x
 	Split(L, x - 1, L, p); //在进行抛 右节点一定为 x 以p为根 ,左节点一定小于 x的,
	p = Merge(t[p].ls, t[p].rs); //我们只需将连接左右儿子,根节点就会被抛弃 
	root = Merge(Merge(L, p), R); // 合并左右子树
}

void Rank(int x) {//计算x的排名
	int L, R;
	Split(root, x - 1, L, R);
	printf("%d\n", t[L].size + 1); //左节点 + 1
	root = Merge(L, R);
}

int kth(int u, int k) { //计算排名为k的点 这个要和上面弄清楚
	if (k == t[t[u].ls].size + 1) {
		return u;
	}
	if (k <= t[t[u].ls].size) {
		return kth(t[u].ls, k);
	}
	if (k > t[t[u].ls].size) {  // 这里是 ls 不是 r 遍历右子树
		return kth(t[u].rs, k - t[t[u].ls].size - 1);
	}
}
void Precursor(int x) {
	int L, R;
	Split(root, x - 1, L, R);
	printf("%d\n", t[kth(L, t[L].size)].key);//这个size是节点的个数 刚好就是排名最后的点的位置
	root = Merge(L, R);
}
void Successor(int x) { //右子树的第一个点
	int L, R;
	Split(root, x, L, R);
	printf("%d\n", t[kth(R, 1)].key); // 和上面同理
	root = Merge(L, R);
}

int main() {
	srand(time(NULL));
	int n;
	cin >> n;
	while (n--) {
		int opt, x;
		cin >> opt >> x;
		switch (opt) {
		case 1:Insert(x); break;
		case 2:Del(x); break;
		case 3:Rank(x); break;
		case 4:printf("%d\n", t[kth(root, x)].key); break;
		case 5:Precursor(x); break;
		case 6:Successor(x); break;
		}
	}
	return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Kafka-Kerberos下执行shell命令】Kafka在Kerberos环境下如何操作shell命令

【Kafka-Kerberos下执行shell命令】Kafka在Kerberos环境下如何操作shell命令1&#xff09;jaas.conf2&#xff09;client.properties3&#xff09;执行命令当大数据集群部署了 Kerberos 认证操作之后&#xff0c;在服务器上操作 kafka shell 命令就会出现认证相关的异常&#x…

【hello Linux】Linux开发工具

目录 1. vim&#xff1a;文本编辑器 1.1 各种模式的切换 补充&#xff1a;ctrl r命令 1.2 命令模式的操作 1.3 插入模式的操作 1.4 底行模式的操作 1.5 配置vim环境 1.6 配置亲属关系 2. gcc/g&#xff1a;编译器 2.1 预处理&#xff1a; 2.2 编译&#xff1a; 2.3 汇编&#x…

Gsum: A General Framework for Guided Neural Abstractive Summarization 论文笔记

Gsum: A General Framework for Guided Neural Abstractive Summarization 论文笔记 Year: 2021 Venue: NAACL Institution: CMU Code: https://github.com/neulab/guided_summarization Overview 这篇文章力求解决的问题是如何控制文本摘要生成&#xff0c;尤其注重可信度方…

floyed 4.9

今天是周末,也就不为难自己了,学习了一下floyed算法(确实简单哈哈),这个算法也是最短路的一种 在一副图中,a到b的距离可能不是直接的ab边最短,这时候就要用到floyed的思想了,可能是a到c到b比直接a到b最短.所以我们要枚举一边才可以知道a到b的最短的路线 这就是floyed的大概 …

7年测试,从功能测试到测试开发,我总算证明了自己

我感觉我是一个比较有发言权的人吧&#xff0c;我在测试行业爬模滚打7年&#xff0c;从点点点的功能测试到现在成为测试开发工程师&#xff0c;工资也翻了几倍&#xff1b; 个人觉得&#xff0c;测试的前景并不差&#xff0c;只要自己肯努力&#xff1b;我刚出来的时候是在鹅厂…

FL Studio21中文版D编曲数字音乐工作软件

随着现在人们的生活水平不断提高&#xff0c;我们的精神生活也越来越丰富&#xff0c;对于现在的年轻人来说&#xff0c;DJ舞曲是一个较受欢迎的领域&#xff0c;有许多年轻人对DJ这个职业感兴趣&#xff0c;想要深入了解DJ编曲这份工作&#xff0c;那么今天我们就来说一说DJ编…

聊聊ChatGPT无法取代的7个工作

ChatGPT——全世界都在谈论的非常流行的人工智能工具。自从 2022 年 11 月 30 日推出以来&#xff0c;ChatGPT 就被证明是执行复杂任务并简化它们的一体式工具。无论是通过 UPenn 的沃顿商学院 MBA 考试&#xff0c;撰写常春藤盟校入学论文&#xff0c;还是完成简单的任务&…

Django自带的Admin后台中如何获取当前登录用户

需求背景 在使用Django快速开发一个IT 电脑、显示器资产管理小系统的时候&#xff0c;遇到一个问题是&#xff0c;当变更资产设备&#xff08;新增、修改、删除&#xff09;的时候&#xff0c;能记录是谁在什么时间进行的变更。 确认的是肯定是登录状态&#xff0c;但是在使用…

算法学习|动态规划 LeetCode 647. 回文子串、516.最长回文子序列

动态规划一、回文子串思路实现代码二、最长回文子序列思路实现代码&#xff08;希望自己能总结出做过的动态规划题&#xff01;要开始回顾之前刷过的题了&#xff09; 一、回文子串 给定一个字符串&#xff0c;你的任务是计算这个字符串中有多少个回文子串。具有不同开始位置或…

回收站数据恢复的方法技巧

​最近有网友反映将一些不经常使用的文件放入回收站后忘记了&#xff0c;清空回收站后想要再次使用文件却怎么都还原不了&#xff0c;想利用回收站数据恢复软件经恢复&#xff0c;咨询有哪些回收站数据恢复软件推荐&#xff0c;下面就给大家推荐回收站数据恢复软件使用方法。 …

数据库开发重点存档

2023春数据库开发复习 T1 视图可以用的几个场景&#xff1f; 不同表字段聚合、信息重组&#xff1a;当某个查询涉及多表连接、次数频繁时&#xff0c;可以创建视图隐藏底层表的复杂性&#xff0c;简化查询。 控制权限&#xff1a;根据不同用户的权限&#xff0c;可以建立不同…

星巴克创始人第三次重出江湖

星巴克创始人第三次出山&#xff0c;与中国有关 中国咖啡连锁竞争白热化 星巴克诞生于1985年&#xff0c;爷爷级的公司 趣讲大白话&#xff1a;百年老店不容易 【趣讲信息科技135期】 **************************** 将心注入 星巴克创始人自传 创始人的激情、执行力、团队建设很…

HttpRunner3.x 源码解析(5)-runner.py

首先看下生成的pytest文件 from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCaseclass TestCaseLogin(HttpRunner):config (Config("登录成功").variables(**{"password": "tester", "expect_foo2": "co…

17_I.MX6ULL_内部RTC

目录 I.MX6U RTC简介 相关寄存器 实验源码 I.MX6U RTC简介 实时时钟是很常用的一个外设,通过实时时钟我们就可以知道年、月、日和时间等信息。因此在需要记录时间的场合就需要实时时钟,可以使用专用的实时时钟芯片来完成此功能,但是现在大多数的MCU或者MPU内部就已经自带了…

一、Locust快速 入门

1 . 介绍 Locust 是一种易于使用、可编写脚本且可扩展的性能测试工具。 您可以在常规 Python 代码中定义用户的行为&#xff0c;而不是被困在 UI 或限制性领域特定语言中。 这使得 Locust 可以无限扩展并且对开发人员非常友好。 用普通的旧 Python 编写测试场景 如果您希望…

【华为OD机试】1038 - 学英语

文章目录一、题目&#x1f538;题目描述&#x1f538;输入输出&#x1f538;样例1二、代码参考作者&#xff1a;KJ.JK&#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x…

基于价值认同的需求侧电能共享分布式交易策略(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

惊喜开箱!品牌可以从 Ledger 引领的顶级体验之一中学到什么?

Ledger 是加密货币硬件钱包的领先供应商&#xff0c;它通过进入 The Sandbox 并创建游戏化体验来扩大其教育计划&#xff0c;从而在虚拟世界中掀起波澜。通过在这个令人兴奋的新空间打造自己的品牌&#xff0c;Ledger 正在接触更广泛的受众&#xff0c;并以有趣的方式与人们互动…

回收站清空了怎么恢复?快来get实用方法!

案例&#xff1a;回收站清空了怎么恢复&#xff1f; 【真的栓Q了&#xff0c;我刚点击回收站&#xff0c;不知道按错了什么&#xff0c;回收站被清空了&#xff0c;大家有什么方法可以恢复回收站里的文件吗&#xff1f;请大家给我出出主意吧&#xff01;谢谢啦&#xff01;】 …

不愧是比亚迪!

最近这段时间&#xff0c;因为我自己准备买车嘛&#xff0c;然后先后去试驾了比亚迪汉、小鹏P7i、蔚来ET5、智己LS7这几辆车&#xff0c;接下来想分4篇文章依次给大家分享一下这四个品牌的车试驾体验。比亚迪汉小鹏P7i蔚来ET5这四个品牌总共花了三天时间&#xff0c;也算是比较…