树套树 (线段树+splay)

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

最简单的是线段树套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.mfbz.cn/a/191761.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

jenkins流水线(pipline)实例

1、pipline 语法介绍 声明式的pipeline语法格式 1. 所有的声明都必须包含在pipeline{}中 2. 块只能有节段&#xff0c;指令&#xff0c;步骤或者赋值语句组成 3. 阶段&#xff1a;agent&#xff0c;stages&#xff0c;post&#xff0c;steps 4. 指令&#xff1a;environment&a…

2017年五一杯数学建模B题自媒体时代的消息传播问题解题全过程文档及程序

2017年五一杯数学建模 B题 自媒体时代的消息传播问题 原题再现 电视剧《人民的名义》中人物侯亮平说&#xff1a;“现在是自媒体时代&#xff0c;任何突发性事件几分钟就传播到全世界。”相对于传统媒体&#xff0c;以互联网技术为基础的自媒体以其信息传播的即时性、交往方式…

C#,数值计算——插值和外推,RBF_fn 与 RBF_gauss 的计算方法与源程序

1 文本格式 using System; namespace Legalsoft.Truffer { public interface RBF_fn { double rbf(double r); } } ---------------------------------------------- using System; namespace Legalsoft.Truffer { public class RBF_gauss : RBF…

如何通过nginx进行服务的负载均衡

简单介绍 随着互联网的发展&#xff0c;业务流量越来越大并且业务逻辑也越来越复杂&#xff0c;单台服务器的性能及单点故障问题就凸显出来了&#xff0c;因此需要多台服务器组成应用集群&#xff0c;进行性能的水平扩展以及避免单点故障的出现。应用集群是将同一应用部署到多台…

上海亚商投顾:北证50指数大涨 逾百只北交所个股涨超10%

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一.市场情绪 沪指11月24日震荡调整&#xff0c;深成指、创业板指盘中跌超1%。北证50指数大涨超6%&#xff0c;北交所个股持…

虚拟化逻辑架构: LBR 网桥基础管理

目录 一、理论 1.Linux Bridge 二、实验 1.LBR 网桥管理 三、问题 1.Linux虚拟交换机如何增删 一、理论 1.Linux Bridge Linux Bridge&#xff08;网桥&#xff09;是用纯软件实现的虚拟交换机&#xff0c;有着和物理交换机相同的功能&#xff0c;例如二层交换&#…

redis key

不管是&#xff1a;规则&#xff0c;还是其他规则&#xff0c;定义好就可以了。其实没有太多要求的。 1&#xff09;冒号分割类似那种yaml在客户端显示树结构 2&#xff09;其他分割类似那种properties在客户端显示列表结构

数组栈的实现

1.栈的概念及结构 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作 进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底 栈中的数据元素遵守后进先出LIFO,&#xff08;Last In First Out&#xff09;的原则 压栈&…

【赠书第8期】工程效能十日谈

文章目录 前言 1 工程效能十日谈 1.1 制定清晰的目标和计划 1.2 引入先进的技术和工具 1.3 建立有效的沟通机制 1.4 灵活应对变化 1.5 确保资源充足 1.6 进行有效的风险管理 1.7 进行持续的监控和评估 1.8 优化团队合作 1.9 注重质量管理 1.10 进行项目总结和反思 …

PyEcharts-Faker的介绍

1 PyEcharts-Faker from pyecharts.faker import Faker方法属性说明对应内容Faker.clothes[“衬衫”, “毛衣”, “领带”, “裤子”, “风衣”, “高跟鞋”, “袜子”]Faker.values()[106, 111, 145, 33, 20, 138, 141]Faker.drinks[“可乐”, “雪碧”, “橙汁”, “绿茶”,…

【管理运筹学】背诵手册(六)| 图与网络分析(基本概念、最小支撑树问题、最短路问题)

六、图与网络分析 基本概念、术语 某个边的两个端点相同&#xff0c;称为环&#xff1b;两点之间有多于一条的边&#xff0c;成为多重边。一个无环、无多重边的图称为简单图&#xff0c;无环但允许有多重边的图称为多重图。 次&#xff1a;以 v i v_i vi​ 为端点的边的数目…

Redis序列化操作

目录 1.protostuff 的 Maven 依赖 2.定义实体类 3.序列化工具类 ProtostuffSerializer 提供了序列化和反序列化方法 4.测试 利用 Jedis 提供的字节数组参数方法&#xff0c;如&#xff1a; public String set(String key, String value) public String set(byte[] key…

IDEA DeBug

文章目录 01_Debug简介和意义02_IDEA中的Debug步骤03_跳转到当前代码执行的行04_步过调试的使用05_步入调试的使用06_强制步入调试的使用07_步出调试的使用08_回退断点调试的使用09_运行到光标处10_计算表达式11_条件断点12_多线程调试 01_Debug简介和意义 什么是程序DeBug&am…

人力资源管理后台 === 主页模块

目录 1.获取用户资料在Vuex中共享 2.显示用户头像和用户名 3.处理头像为空的场景 4.处理token失效的问题 5.调整下拉菜单&#xff0c;实现退出登录 6.修改密码功能实现 6.1-修改密码-弹出层 6.2-修改密码-表单结构 6.3-修改密码-表单校验 6.4-修改密码-确定和取消 7.…

设计模式精讲:掌握单例模式的实现与优化

掌握单例模式的实现与优化 一、引言&#xff1a;如何学习设计模式&#xff1f;二、前置知识&#xff1a;对象的创建的销毁2.1、拷贝构造2.2、拷贝赋值构造2.3、移动构造2.4、移动赋值构造 三、单例模式的定义四、单例模式的实现与优化4.1、版本一4.2、版本二4.3、版本三4.4、版…

均匀球形分布的随机三维单位向量

生成具有均匀球形分布的随机三维单位向量[参考] import numpy as np import matplotlib.pyplot as plt def random_three_vector():"""Generates a random 3D unit vector (direction) with a uniform spherical distributionAlgo from http://stackoverflow.c…

论文阅读:C2VIR-SLAM: Centralized Collaborative Visual-Inertial-Range SLAM

前言 论文全程为C2VIR-SLAM: Centralized Collaborative Visual-Inertial-Range Simultaneous Localization and Mapping&#xff0c;是发表在MDPI drones&#xff08;二区&#xff0c;IF4.8&#xff09;上的一篇论文。这篇文章使用单目相机、惯性测量单元( IMU )和UWB设备作为…

Node——npm包管理器的使用

Node.js使用npm对包进行管理&#xff0c;其全称为Node Package Manager&#xff0c;开发人员可以使用它安装、更新或者卸载Node.js的模块 1、npm包管理器基础 1.1、npm概述 npm是Node.js的标准软件包管理器&#xff0c;其在2020年3月17日被GitHub收购&#xff0c;而且保证永…

1.9 字符数组

1.9 字符数组 一、字符数组概述二、练习 一、字符数组概述 所谓字符数组&#xff0c;就是char类型的数组&#xff0c;比如 char a[]&#xff0c;是C语言中最常用的数组类型&#xff0c;先看一个程序 #include <stdio.h> #define MAXLINE 1000 //最大行长度限制 int get…

软件介绍02- flameshot截图软件(linux系统可用)

1 软件介绍 在Windows和mac平台一直都使用着snipaste截图&#xff0c;非常好用&#xff0c;又能够钉图。遗憾是并没有开发linux版本&#xff0c;真不知道为什么。 好在终于找到一款截图软件&#xff0c;flameshot截图软件&#xff0c;可以平替snipaste。 下载网址&#xff1a;…