树套树 (线段树+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); // 转一次xelse rotate(y); // 转一次yrotate(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位置数值改为xupdate(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.mzph.cn/news/171787.shtml

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

相关文章

【数据结构】堆(C语言)

今天我们来学习堆&#xff0c;它也是二叉树的一种&#xff08;我滴神树&#xff01;&#xff09; 目录 堆的介绍&#xff1a;堆的代码实现&#xff1a;堆的结构体创建&#xff1a;堆的初始化&#xff1a;堆的销毁&#xff1a;堆的push&#xff1a;堆的pop&#xff1a;判空 &am…

力扣373场周赛题解

第一题&#xff1a; 这个题是一个简单题&#xff0c;数据范围也特别小&#xff0c;所以直接使用模拟方式暴力解答。 直接进行行移动的过程&#xff0c;然后检查移动后的结果是否与移动前相同。 代码&#xff1a; ​ public class Solution {// 将指定行循环右移k次pri…

6.前端--CSS-基础选择器【2023.11.26】

1.CSS基本选择器 标签选择器&#xff1a; 标签选择器&#xff08;元素选择器&#xff09;是指用 HTML 标签名称作为选择器&#xff0c;按标签名称分类&#xff0c;为页面中某一类标签指定统一的 CSS 样式。标签选择器可以把某一类标签全部选择出来&#xff0c;比如所有的 <…

ns-3安装教程

1️⃣ VMware安装Ubuntu虚拟机 VMware安装Ubuntu 18.04虚拟机 6.桥接模式网络配置往后都不看 VMware16 安装Ubuntu22.04.1 服务器版操作系统教程 这块主要看怎么从清华镜像下载ubuntu 本人安装的是ubuntu22.04.3 2️⃣ 安装ns3 【ns-3】零基础安装教程 安装依赖库时遇到了问题 …

Bun 1.0 正式发布,爆火的前端运行时,速度遥遥领先!

目录 Bun Github 41.6k 另外&#xff0c;Bun.js 原生支持了数百个 Node.js 和 Web API&#xff0c;包括约 90% 的 Node-API 函数(fs、path、Buffer 等)。 一、包子1.0Bun 1.0终于来了。 二、Bun 是一个一体化工具包 为什么包子存在 Bun 的目标很简单&#xff1a;消除缓慢…

现货黄金区间交易的两个要点

在现货黄金市场中&#xff0c;我们常碰到横盘区间行情。有区间&#xff0c;就终究会出现突破&#xff0c;因为金价不可能缺乏方向而一直在区间内运行。那既然要突破&#xff0c;我们又应当如何应对和交易呢&#xff1f;下面我们就来讨论一下。 切忌在突破发生时马上跟随突破方向…

个人成长|信奉长期主义,就要多做可积累有复利的事。

哈喽啊&#xff0c;大家好&#xff0c;我是雷工&#xff01; 最近有个哥们儿吐槽&#xff0c;说他们公司人事找他谈话&#xff0c;要给降工资&#xff0c;他不同意。 过了没几天又说&#xff1a; “定了&#xff0c;全员降薪”。 “你同意了&#xff1f;” “不同意&#xff0…

CV计算机视觉每日开源代码Paper with code速览-2023.11.21

点击CV计算机视觉&#xff0c;关注更多CV干货 论文已打包&#xff0c;点击进入—>下载界面 点击加入—>CV计算机视觉交流群 1.【基础网络架构&#xff1a;Transformer】Multi-entity Video Transformers for Fine-Grained Video Representation Learning 论文地址&…

Qt 串口编程-从入门到实战

1. Qt 串口通信流程解析 1.1 串行通信和并行通信对比 并行通信适合距离较短的通信&#xff0c;且信号容易受干扰&#xff0c;成本高串口通讯-设备&#xff08;蓝牙&#xff0c; wifi&#xff0c; gprs&#xff0c; gps&#xff09; 1.2 Qt 串口通信具体流程 1. 创建 QSerial…

Java基于springboot+vue开发服装商城小程序

演示视频&#xff1a; 小程序 https://www.bilibili.com/video/BV1rM411o7m4/?share_sourcecopy_web&vd_source11344bb73ef9b33550b8202d07ae139b 管理员 https://www.bilibili.com/video/BV1fc411D7V3/?share_sourcecopy_web&vd_source11344bb73ef9b33550b8202d07ae…

计算机中vcomp140.dll丢失的解决方法,一键修复vcomp140.dll缺失问题

vcomp140.dll是Visual C 2015 Redistributable的一个组件&#xff0c;它是运行一些基于Visual Studio开发的软件所必需的。当你在运行某些程序时&#xff0c;可能会遇到“找不到vcomp140.dll”的错误提示&#xff0c;这通常是由于系统缺少这个组件导致的。本文将介绍vcomp140.d…

服务运营 |精选:病人向何处去?医院调度的几种建模方法(上)

推文作者&#xff1a; Shutian Li 编者按&#xff1a; 住院流程&#xff08;Inpatient Flow&#xff09;是一种通过协调和优化医院内部流程&#xff0c;以提高患者入院至出院期间的效率和质量的方法。住院流程通常通过医院内部信息系统和协同工作流程进行管理&#xff0c;以确…

ArcGIS中基于人口数据计算人口密度的方法

文章目录 一、密度分析原理二、点密度分析三、线密度分析四、核密度分析一、密度分析原理 密度分析是指根据输入的要素数据集计算整个区域的数据聚集状况,从而产生一个联系的密度表面。通过密度计算,将每个采样点的值散步到整个研究区域,并获得输出栅格中每个像元的密度值。…

C语言——深入理解指针(2)

目录 1. 数组名 2. 指针访问数组 3. 一维数组的传参&#xff08;本质&#xff09; 4. 冒泡排序 5. 二级指针 6. 指针数组&#xff08;指针的数组&#xff09; 7. 指针数组模拟二维数组 1. 数组名 在之前的代码中我们使用指针访问过数组的内容。 int arr[10] {1,2,3,4…

针对c语言的scanf读取字符和字符串解析

在scanf函数中&#xff0c;格式字符串里的空格字符有特定的作用。 当你在格式字符串里放置一个空格时&#xff0c;scanf会尝试匹配并消耗输入中的一个或多个空白字符&#xff08;包括空格、制表符或换行符&#xff09;。换句话说&#xff0c;它会跳过任何空白字符&#xff0c;…

jQuery_09 事件的绑定与使用(on)

jQuery使用on绑定事件 jQuery可以给dom对象添加事件 在程序执行期间动态的处理事件 1. $("选择器").事件名称(事件处理函数) $("选择器") &#xff1a; 选择0或者多个dom对象 给他们添加事件 事件名称&#xff1a;就是js中事件名称去掉on的部分 比如单击…

js逆向-JS加密破解

一、常见五种js加密手段 &#xff08;一&#xff09;加密位置&#xff1a; 1.Request Payload 加密 2.Request Headers 加密 3.Request URL params 参数加密 4.Response Data 数据加密 5.JS代码混淆加密 &#xff08;二&#xff09;加密算法 base64 编码 哈希算法&…

抖音视频怎么提取动图?手机视频转gif方法

抖音是人们休闲娱乐消遣时光必备的短视频软件&#xff0c;当我们想要把好玩有趣的抖音短视频转换成gif动画时&#xff0c;要怎么操作呢&#xff1f;通过使用gif动图制作&#xff08;https://www.gif.cn/&#xff09;网站-GIF中文网&#xff0c;手机自带浏览器&#xff0c;上传视…

Leetcode—45.跳跃游戏II【中等】

2023每日刷题&#xff08;四十&#xff09; Leetcode—45.跳跃游戏II 贪心法思想 实现代码 #define MAX(a, b) (a > b ? (a) : (b))int jump(int* nums, int numsSize) {int start 0;int end 1;int ans 0;int maxStride 0;while(end < numsSize) {maxStride 0;fo…