树套树 (线段树+splay)

news2024/11/28 20:56:24

树套树,就是线段树、平衡树、树状数组等数据结构的嵌套。

最简单的是线段树套set,可以解决一些比较简单的问题,而且代码根线段树是一样的只是一些细节不太一样。

本题中用的是线段树套splay,代码较长。

树套树中的splay和单一的splay原理是一样的,只不过是建了很多的splay树,因为不止一个,所以跟板子不同的是,大部分函数都要传splay的根节点规定起点。

而线段树中存储的就是每个区间对应的splay的root节点。

只要线段树和splay板子都懂了,这一题就很好理解。

const int mod = 1e9 + 7, INF = 2147483647;
const int N = 1e7+ 10;
int n, m;
struct Node {
	int s[2], p, v; // 左右儿子、父节点、值
	int size, cnt; // 子树大小、懒标记
	void init(int _v, int _p) { // 初始化函数
		v = _v, p = _p;
		cnt = size =  1;
	}
} tr[N];
int L[N], R[N], T[N], idx;
int w[N];

void pushup(int u) { // 向上更新传递,与线段树一样
	tr[u].size = tr[tr[u].s[0]].size + tr[tr[u].s[1]].size + tr[u].cnt;
}

void rotate(int x) { // 核心函数
	int y = tr[x].p, z = tr[y].p;
	int k = tr[y].s[1] == x;
	tr[z].s[tr[z].s[1] == y] = x, tr[x].p = z;
	tr[y].s[k] = tr[x].s[k ^ 1], tr[tr[x].s[k ^ 1]].p = y;
	tr[x].s[k ^ 1] = y, tr[y].p = x;
	pushup(y), pushup(x);
}

void splay(int& root, int x, int k) { // 将x节点旋转到k节点下
	while(tr[x].p != k) { //
		int y = tr[x].p; // x节点的父节点
		int z = tr[y].p; // x节点的父节点的父节点
		if(z != k) // 向上旋转
			if((tr[y].s[1] == x) != (tr[z].s[1] == y)) rotate(x); // 转一次x
			else rotate(y); // 转一次y
		rotate(x); // 转一次x
	}
	if(!k) root = x; // 更新root节点
}

void upper(int& root, int v) { // 将v值节点转到根节点
	int u = root; // 根节点
	while(tr[u].s[v > tr[u].v] && tr[u].v != v) // 存在则找到v值节点,不存在则找到v值节点的前驱或者后继节点
		u = tr[u].s[v > tr[u].v]; // 向下寻找
	splay(root, u, 0); // 将u节点旋转到跟节点
}

int get_prev(int& root, int v) { // 获取v值的前驱节点,严格小于v的最大节点
	upper(root, v); // 将v值节点转到根节点
	if(tr[root].v < v) return root; // 若是该值在树中不存在,根节点就是v的前驱或者后继节点
	int u = tr[root].s[0]; // 前驱节点在左子树的最右边
	while(tr[u].s[1]) u = tr[u].s[1]; // 找到最右边的一个节点
	return u;
}

int get_next(int& root, int v) { // 获取某值的后继节点,严格大于v的最小节点
	upper(root, v); // 将v值节点转到根节点
	if(tr[root].v > v) return root; // 若是该值在树中不存在,根节点就是v的前驱或者后继节点
	int u = tr[root].s[1]; // 后继节点在右子树的最左边
	while(tr[u].s[0]) u = tr[u].s[0]; // 找到最左的节点,就是最小的节点
	return u; // 返回节点
}

void insert(int& root, int v) { // 在二叉树中插入一个值
	int u = root, p = 0; // p维护为当前节点的父节点
	while(u && tr[u].v != v) // 没找到则一直向下寻找
		p = u, u = tr[u].s[v > tr[u].v]; // 更新父节点,更新当前节点
	if(u) tr[u].cnt ++; // v值的节点已经存在则直接加一即可
	else { // 不存在则创建节点
		u = ++ idx; // 分配节点序号
		if(p) tr[p].s[v > tr[p].v] = u; // 将父节点也就是前驱节点指向当前节点
		tr[u].init(v, p); // 初始化当前节点的值、父节点信息
	}
	splay(root, u, 0); // 将u节点旋转到根节点下
}

int get_k(int root, int v) { // 获得树中有多少比v小的数
	int u = root, res = 0;
	while(u) {
		if(tr[u].v < v) res += tr[tr[u].s[0]].size + tr[u].cnt, u = tr[u].s[1];
		else u = tr[u].s[0];
	}
	return res;
}

void remove(int& root, int v) { // 删除一个值为v的节点
	int prev = get_prev(root, v), nex = get_next(root, v); // 获取该节点的前驱以及后继节点。
	splay(root, prev, 0), splay(root, nex, prev); // 将前继节点旋转到根节点,将后继节点旋转到前驱节点下面也就是根节点下面
	int w = tr[nex].s[0]; // 后继节点的左子树就是v的节点
	if(tr[w].cnt > 1) tr[w].cnt --, splay(root, w, 0); // 该节点的v不止存在一个,减一,w节点旋转到根节点
	else tr[nex].s[0] = 0, splay(root, nex, 0); // 唯一,那么直接把后继节点的左子树指向空也就是0即可
}

void update(int& root, int x, int y) { // 将一个x值点改为y值
	remove(root, x); // 先删除
	insert(root, y); // 再插入
}

void build(int u, int l, int r) {
	L[u] = l, R[u] = r; // 存储某个节点的左右边界
	insert(T[u], -INF), insert(T[u], INF); // 插入哨兵
	for(int i = l; i <= r; i ++) insert(T[u], w[i]); // 初始化线段树每个节点的平衡树
	if(l == r) return ;
	int mid = l + r >> 1;
	build(u << 1, l, mid); // 建左子树
	build(u << 1 | 1, mid + 1, r); // 建右子树
}


int query(int u, int a, int b, int x) { // 查询区间a,b之间有多少比x值小的数
	if(a <= L[u] && R[u] <= b)  return get_k(T[u], x) - 1;
	int mid = L[u] + R[u] >> 1, res = 0;
	if(a <= mid) res += query(u << 1, a, b, x); // 查询左子树中有多少是该区间并且小于x的数
	if(mid < b) res += query(u << 1 | 1, a, b, x); // 查询右子树中有多少是该区间并且小于x的数
	return res;
}

void change(int u, int p, int x) { // 将线段树中p位置数值改为x
	update(T[u], w[p], x); // 修改当前节点中平衡树中的值
	if(L[u] == R[u]) return ;
	int mid = L[u] + R[u] >> 1;
	if(p <= mid) change(u << 1, p, x); // 修改左子树
	else change(u << 1 | 1, p, x); // 修改右子树
}

int query_prev(int u, int a, int b, int x) { // 查询再该区间中x的前驱节点
	if(a <= L[u] && R[u] <= b) return tr[get_prev(T[u], x)].v; // 该函数为查找当前子树中x的前驱节点
	int mid = L[u] + R[u] >> 1, res = -INF;
	if(a <= mid) res = max(res, query_prev(u << 1, a, b, x)); // 递归左子树
	if(mid < b) res = max(res, query_prev(u << 1 | 1, a, b, x)); // 递归右子树
	return res; // 返回左右子树中的最大值
}

int query_next(int u, int a, int b, int x) { // 查询再该区间中x的后继节点
	if(a <= L[u] && R[u] <= b)  return tr[get_next(T[u], x)].v; // 该函数为查找当前子树中x的后继节点
	int mid = L[u] + R[u] >> 1, res = INF;
	if(a <= mid) res = min(res, query_next(u << 1, a, b, x));
	if(mid < b) res = min(res, query_next(u << 1 | 1, a, b, x));
	return res; // 返回左右子树中的最小值
}

int get_rank_to_tr(int a, int b, int x) { // 查找区间内排名第x的数 
	int l = 0, r = 1e8;
	while(l < r) { // 通过二分获得答案,因为只能判断某个数在区间内的排名。 
		int mid = l + r + 1 >> 1;
		if(query(1, a, b, mid) + 1 <= x) l = mid; // 
		else r = mid - 1;
	}
	return r;
}

inline void sovle() {
	cin >> n >> m;
	for(int i = 1; i <= n; i ++)cin >> w[i];
	build(1, 1, n);
	while(m --) {
		int op, a, b, x;
		cin >> op >> a >> b;
		if(op != 3) cin >> x;
		if(op == 1) cout << query(1, a, b, x) + 1 << endl;
		if(op == 2) cout << get_rank_to_tr(a, b, x) << endl;
		if(op == 3) {
			change(1, a, b);
			w[a] = b;
		}
		if(op == 4) cout << query_prev(1, a, b, x) << endl;
		if(op == 5) cout << query_next(1, a, b, x) << endl;
	}

}

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

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

相关文章

基于springboot实现农机电招平台系统项目【项目源码+论文说明】

基于springboot实现农机电招平台系统演示 摘要 随着农机电招行业的不断发展&#xff0c;农机电招在现实生活中的使用和普及&#xff0c;农机电招行业成为近年内出现的一个新行业&#xff0c;并且能够成为大群众广为认可和接受的行为和选择。设计农机电招平台的目的就是借助计算…

C++ day37 贪心算法 单调递增的数字 监控二叉树

题目1&#xff1a;738 单调递增的数字 题目链接&#xff1a;单调递增的数字 对题目的理解 返回小于或等于n的最大数字&#xff0c;且数字是单调递增&#xff08;单调递增数字的定义&#xff1a;每个相邻位上的数字满足x<y&#xff09; 贪心算法 注意本题的遍历顺序是从…

写 SVG 动画必看!SVG系列文章1-简介

1、SVG是什么 SVG 是一种 XML 语言&#xff0c;类似 XHTML&#xff0c;可以用来绘制矢量图形&#xff0c;例如下面展示的图形。SVG 可以通过定义必要的线和形状来创建一个图形&#xff0c;也可以修改已有的位图&#xff0c;或者将这两种方式结合起来创建图形。图形和其组成部分…

Vatee万腾科技的未来探险:Vatee数字创新的独特发现

在科技的浩瀚海洋中&#xff0c;Vatee万腾科技如一艘探险船般&#xff0c;勇敢地驶向未知的数字化领域。这次未来的探险&#xff0c;不仅是一场科技创新的冒险&#xff0c;更是对数字化时代的独特发现和深刻探讨。 Vatee万腾科技视科技创新为一座高峰&#xff0c;而他们的未来探…

探究Kafka原理-5.Kafka设计原理和生产者原理解析

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱吃芝士的土豆倪&#xff0c;24届校招生Java选手&#xff0c;很高兴认识大家&#x1f4d5;系列专栏&#xff1a;Spring源码、JUC源码、Kafka原理&#x1f525;如果感觉博主的文章还不错的话&#xff0c;请&#x1f44…

SATA信息传输FIS结构总结通过实例代码(快速)掌握(二)

目录 一、简介二、总体介绍2.1 详细FIS传输过程2.2 FIS内容详解 三、FIS实例3.1 构造一个write FIS3.1.1 WRITE FIS内容3.1.2 协议分析仪trace分析3.1.3 write过程总trace 3.2 构造一个read FIS3.2.1 READ FIS内容3.2.2 协议分析仪 read trace3.2.3 read过程总trace 3.3 FIS和C…

【自制】我与GPT3.5合作,一天制作一个生成手写文字图片的软件

视频讲解地址&#xff1a;https://www.bilibili.com/video/BV1uC4y127ME/ bgm我与GPT3.5合作&#xff0c;一天制作一个生成手写文字图片的软件 请给出一个程序&#xff0c;左边显示一个图片&#xff0c;将图片放入进去&#xff0c;可以在里面画出一个框&#xff0c; 右边窗口…

MQTT客户端MQTT.fx 1.7.1下载、安装和界面介绍

MQTT.fx是一款基于Eclipse Paho&#xff0c;使用Java语言编写的MQTT客户端工具。支持通过Topic订阅和发布消息&#xff0c;用来前期和物理云平台调试非常方便。 1.下载 1.1.访问官方下载地址下载&#xff0c;但是下载不到1.7.1版本 1.2.在连接网页末尾点击立即下载&#xff0c;…

Linux(9):正规表示法与文件格式化处理

简单的说&#xff0c;正规表示法就是处理字符串的方法&#xff0c;他是以行为单位来进行字符串的处理行为&#xff0c;正规表示法透过一些特殊符号的辅助&#xff0c;可以让使用者轻易的达到【搜寻/删除/取代】某特定字符串的处理程序。 正规表示法基本上是一种【表示法】&…

【从入门到起飞】JavaSE—多线程(2)(lock锁,死锁,等待唤醒机制)

文章目录 &#x1f33a;lock锁⭐获得锁⭐释放锁✨注意&#x1f3f3;️‍&#x1f308;代码实现&#x1f388;细节 &#x1f33a;死锁⭐解决方法 &#x1f384;等待唤醒机制⭐代码实现&#x1f388;注意 &#x1f6f8;使用阻塞队列实现等待唤醒机制 &#x1f354;线程的六种状态…

AOP + 自定义注解实现日志打印

1. 先定义个注解&#xff0c;让它作用于方法上 Target({ElementType.METHOD}) Retention(RetentionPolicy.RUNTIME) public interface Loggable {}2. 定义切面 Aspect Component Slf4j public class LogMethodCallAspect {Pointcut("annotation(com.wy.spring_demo.aop.…

1panel在应用商店里面安装jenkins

文章目录 目录 文章目录 前言 一、使用步骤 1.1 填写安装参数 1.2 在界面中进入容器拿到自动生成的jenkins密码 前言 一、使用步骤 1.1 填写安装参数 在应用商店里面搜索jenkins,然后点击安装 填写参数 1.2 在界面中进入容器拿到自动生成的jenkins密码 命令 cat /var/jenki…

【数据结构与算法篇】一文详解数据结构之二叉树

树的介绍及二叉树的C实现 树的概念相关术语树的表示 树的概念 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一 个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树&#xff0c; 也就是说它是根朝上&#xff0c;而叶朝…

上海站报名启动! 2023年开源产业生态大会OpenHarmony生态分论坛

作为年内开源领域不容错过的科技盛宴&#xff0c;2023年开源产业生态大会将于12月19日在上海盛大开幕。本次活动由上海市经济和信息化委员会、上海市科学技术协会和"科创中国"开源创新联合体共同指导&#xff0c;上海开源信息技术协会统筹主办。 届时&#xff0c;大会…

WEB渗透—反序列化(六)

Web渗透—反序列化 课程学习分享&#xff08;课程非本人制作&#xff0c;仅提供学习分享&#xff09; 靶场下载地址&#xff1a;GitHub - mcc0624/php_ser_Class: php反序列化靶场课程&#xff0c;基于课程制作的靶场 课程地址&#xff1a;PHP反序列化漏洞学习_哔哩哔_…

黑马程序员索引学习笔记

文章目录 索引的分类从索引字段特性从物理存储从数据结构组成索引的字段个数 InnoDB主键索的Btree高度为多高呢?explain执行计划最左匹配原则索引失效情况SQL提示覆盖索引、回表查询前缀索引索引设计原则 索引的分类 从索引字段特性 主键索引、唯一索引、常规索引、全文索引…

2023 年最新百度智能云千帆大模型 Node.Js 本地测试 / 微信机器人详细教程(更新中)

千帆大模型概述 一站式企业级大模型平台&#xff0c;提供先进的生成式AI生产及应用全流程开发工具链。直接调用ERNIE-Bot 4.0及其他主流大模型&#xff0c;并提供可视化开发工具链&#xff0c;支持数据闭环管理、专属大模型定制、大模型训练调优、插件编排等功能。 千帆大模型…

RHCE---给openlab搭建web网站

作业&#xff1a;请给openlab搭建web网站 网站需求&#xff1a; 1.基于域名 www.openlab.com 可以访问网站内容为 welcome to openlab!!! 2.给该公司创建三个子界面分别显示学生信息&#xff0c;教学资料和缴费网站&#xff0c; 1、基于 www.openlab.com/student 网站访问学生信…

中国一年有457万人确诊癌症!医生提示:这4种食物,再爱吃也要管住嘴

癌症是威胁人类生命健康的重大疾病&#xff0c;癌症的发生因素一直以来都是专家学者重点探索的课题。据世卫组织最新公布的数据显示&#xff0c;食物或与癌症发生之间存在着密切的联系&#xff0c;某些食物的摄入过多可能会增加患癌症的风险&#xff0c;所以我们应该警惕&#…

LeetCode Hot100 124.二叉树中的最大路径和

题目&#xff1a; 二叉树中的 路径 被定义为一条节点序列&#xff0c;序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点&#xff0c;且不一定经过根节点。 路径和 是路径中各节点值的总和。 给你一个二叉树的根节点…