【模板】Splay(伸展树)普通平衡树(数据加强版)/洛谷P6136
题目链接
https://www.luogu.com.cn/problem/P6136
题目大意
需要写一种数据结构,来维护一些非负整数( \(int\) 范围内)的升序序列,其中需要提供以下操作:
- 插入一个整数 \(x\) 。
- 删除一个整数 \(x\) (若有多个相同的数,只删除一个)。
- 查询整数 \(x\) 的排名(排名定义为比当前数小的数的个数 \(+1\) )。
- 查询排名为 \(x\) 的数(如果不存在,则认为是排名小于 \(x\) 的最大数。保证 \(x\) 不会超过当前数据结构中数的总数)。
- 求 \(x\) 的前驱(前驱定义为小于 \(x\) ,且最大的数)。
- 求 \(x\) 的后继(后继定义为大于 \(x\) ,且最小的数)。
其中,初始序列大小为 \(n\) ,询问/操作总数为 \(m\) ,并要求 强制在线 。
数据范围 \(n \leq 10^5, m \leq 10^6\) , 单点时限 \(3s\) 。
题目解析
要求总复杂度为 \(O(N \log N)\) 级别,可以考虑伸展树( \(Splay\) ),对单个节点的查询、修改都维持在 \(O(\log N)\) 级别。
值得注意的是,这里代码中在数据集内多加入了一大( \(INF\) )一小( \(-1\) )两个虚设端点作为平衡树的上下限( \(front, back\) ),这样在实现查找、插入时可以避免越界,并能减少对于某些情况的特殊判断。
参考代码
#include <bits/stdc++.h>
#define N 1200005
#define EMPTY 0
#define INF INT_MAX
#define ll long long
using namespace std;
struct Tree{
int data, dataMin, dataMax, size, fa, child[2];
} t[N]; //其中data, fa, child为节点的基本属性
int cnt, root, front, back;
vector <int> dataset, nodeBin;
inline void read(int &s) { //快读,支持int
s = 0;
int tt = 1, k = getchar();
for (; k < '0' || k > '9'; k = getchar()) if (k == '-') tt = -1;//判断该数正负
for (; k >= '0' && k <= '9'; k = getchar()) s = s * 10+(k ^ 48);//^48相当于-‘0’,较快。
s *= tt;
}
inline void write(ll s) { //快写,支持int和long long
int tt = 0, a[40];
if (s < 0) putchar('-'), s = -s;
do { a[++tt] = s % 10; } while (s /= 10);//用do while就不用特判一个0
while(tt) putchar(48+a[tt--]);
}
inline int checkSize(int x) { return (x == EMPTY) ? 0 : t[x].size;}
inline int checkDataMin(int x) { return (x == EMPTY) ? INF : t[x].dataMin;}
inline int checkDataMax(int x) { return (x == EMPTY) ? -INF : t[x].dataMax;}
inline void updNode(int x) {
t[x].size = checkSize(t[x].child[0]) + checkSize(t[x].child[1]) + 1;
t[x].dataMin = min(t[x].data, min(checkDataMin(t[x].child[0]), checkDataMin(t[x].child[1])));
t[x].dataMax = max(t[x].data, max(checkDataMax(t[x].child[0]), checkDataMax(t[x].child[1])));
}
void rotate(int x, int o)
{
int y = t[x].fa;
if (!y) return;
int z = t[y].fa;
t[y].child[o^1] = t[x].child[o];
if (t[x].child[o] != EMPTY) t[t[x].child[o]].fa = y;
t[x].fa = z;
if (z != EMPTY)
{
if (t[z].child[0] == y) t[z].child[0] = x;
else t[z].child[1] = x;
}
t[x].child[o] = y;
t[y].fa = x;
updNode(y);
updNode(x);
}
void splay(int x)
{
if (x == EMPTY) return;
int y;
while (t[x].fa != EMPTY)
{
y = t[x].fa;
if (t[y].fa == EMPTY) //旋转后为根节点
{
if (t[y].child[0] == x) rotate(x, 1);
else rotate(x, 0);
break;
}
else {
if (t[t[y].fa].child[1] == y)
{
if (t[y].child[0] == x) rotate(x, 1), rotate(x, 0);
else rotate(y, 0), rotate(x, 0);
}
else {
if (t[y].child[1] == x) rotate(x, 0), rotate(x, 1);
else rotate(y, 1), rotate(x, 1);
}
}
}
root = x;
}
inline int mininum(int x) { //找x的子树中序号最小的
while (t[x].child[0] != EMPTY) x = t[x].child[0];
return x;
}
inline int maxinum(int x) { //找x的子树中序号最大的
while (t[x].child[1] != EMPTY) x = t[x].child[1];
return x;
}
inline int succ(int x) { //找x的后继
splay(x);
if (t[x].child[1] == EMPTY) return EMPTY;
return mininum(t[x].child[1]);
}
inline int prec(int x) { //找x的前驱
splay(x);
if (t[x].child[0] == EMPTY) return EMPTY;
return maxinum(t[x].child[0]);
}
int createNode(int data) //新建节点,存放data(优先取用废弃内存池)
{
if (nodeBin.empty())
{
t[++cnt] = (Tree){data, data, data, 1, EMPTY, EMPTY, EMPTY};
return cnt;
}
int x = nodeBin.back();
t[x] = (Tree){data, data, data, 1, EMPTY, EMPTY, EMPTY};
nodeBin.pop_back();
return x;
}
int findKth(int x, int k) //找序号为k的节点
{
while (true)
{
if (x == EMPTY) return EMPTY;
int lc = checkSize(t[x].child[0]);
if (k <= lc) x = t[x].child[0];
else {
if (k == lc+1) return x;
else {x = t[x].child[1], k -= lc+1;}
}
}
}
inline int getKth(int x) { //找节点x的序号k
splay(x);
return checkSize(t[x].child[0]) + 1;
}
void insertKth(int x, int k) //将单节点x插入树中的序号k的位置
{
if (!root) {root = x; return;}
if (k <= 0 || k > t[root].size+1) return;
if (k == 1) {
int y = mininum(root);
if (y == EMPTY) return;
splay(y);
t[y].child[0] = x;
t[x].fa = y;
updNode(y);
return;
}
int y = findKth(root, k-1);
if (y == EMPTY) return;
splay(y);
t[x].child[1] = t[y].child[1];
if (t[y].child[1] != EMPTY) t[t[y].child[1]].fa = x;
t[y].child[1] = EMPTY;
t[x].child[0] = y;
t[y].fa= x;
root = x;
updNode(y);
updNode(x);
}
void deleteKth(int k) //删除树上序号为k的节点
{
if (!root) {return;}
if (k <= 0 || k > t[root].size) return;
if (k == 1) {
int y = mininum(root);
if (y == EMPTY) return;
nodeBin.push_back(y);
splay(y);
if (t[y].child[1] != EMPTY) t[t[y].child[1]].fa = EMPTY, root = t[y].child[1];
else root = 0;
return;
}
int y = findKth(root, k);
if (y == EMPTY) return;
splay(y);
nodeBin.push_back(y);
int z = prec(y);
t[z].child[1] = t[y].child[1];
if (t[y].child[1] != EMPTY) t[t[y].child[1]].fa = z;
t[t[y].child[0]].fa = EMPTY;
root = t[y].child[0];
while (z != EMPTY)
{
updNode(z);
z = t[z].fa;
}
}
int buildTree(int L, int R, int fa) //建树,data取用dataset[L]~[R],L>0
{
if (L > R) return EMPTY;
if (L == R) {
t[L] = (Tree){dataset[L], dataset[L], dataset[L], 1, fa, EMPTY, EMPTY};
return L;
}
int mid = (L+R)/2;
t[mid] = (Tree){dataset[mid], 0, 0, 0, fa, EMPTY, EMPTY};
t[mid].child[0] = buildTree(L, mid-1, mid);
t[mid].child[1] = buildTree(mid+1, R, mid);
updNode(mid);
return mid;
}
inline void clearAll() { //清空全部
cnt = 0;
dataset.clear();
nodeBin.clear();
root = 0;
}
int findDataLeq(int x, int data) //找到x的子树上<=Data的最大序号的节点
{
int a = EMPTY;
if (x == EMPTY) return EMPTY;
if (data < t[x].dataMin) return EMPTY;
if (t[x].child[1] != EMPTY) {
if (data >= checkDataMin(t[x].child[1])) {
a = findDataLeq(t[x].child[1], data);
}
}
if (a != EMPTY) return a;
if (t[x].data <= data) return x;
if (t[x].child[0] != EMPTY) {
if (data >= checkDataMin(t[x].child[0])) {
a = findDataLeq(t[x].child[0], data);
}
}
if (a != EMPTY) return a;
return EMPTY;
}
int findDataGeq(int x, int data) //找到x的子树上>=Data的最小序号的节点
{
int a = EMPTY;
if (x == EMPTY) return EMPTY;
if (data > t[x].dataMax) return EMPTY;
if (t[x].child[0] != EMPTY) {
if (data <= checkDataMax(t[x].child[0])) {
a = findDataGeq(t[x].child[0], data);
}
}
if (a != EMPTY) return a;
if (t[x].data >= data) return x;
if (t[x].child[1] != EMPTY) {
if (data <= checkDataMax(t[x].child[1])) {
a = findDataGeq(t[x].child[1], data);
}
}
if (a != EMPTY) return a;
return EMPTY;
}
inline void printTree() { //将树上每个节点data按序输出
int x = mininum(root);
while (x != EMPTY) {
write(t[x].data);
x = succ(x);
if (x != EMPTY) putchar(' ');
}
putchar('\n');
}
int main()
{
int n, m, last = 0, ans = 0;
read(n); read(m);
dataset.push_back(-2);
dataset.push_back(-1);
for (int i = 0; i < n; ++i) {
int x;
read(x);
dataset.push_back(x);
}
dataset.push_back(INF);
sort(dataset.begin(), dataset.end());
root = buildTree(1, cnt = n+2, EMPTY);
front = 1, back = cnt;
while (m--)
{
int opt, x;
read(opt), read(x);
x ^= last;
switch (opt) {
case 1: //插入整数data->x
splay(back);
insertKth(createNode(x), getKth(findDataLeq(back, x))+1); break;
case 2: //删除整数data->x
splay(front);
deleteKth(getKth(findDataGeq(front, x))); break;
case 3: //查询x的排名
splay(back);
last = getKth(findDataLeq(back, x-1));
ans ^= last;
break;
case 4: //查询第x个data
last = t[findKth(root, x+1)].data;
ans ^= last;
break;
case 5: //查询小于x的最大data
splay(front);
last = t[findDataLeq(front, x-1)].data;
ans ^= last;
break;
case 6: //查询大于x的最小data
splay(back);
last = t[findDataGeq(back, x+1)].data;
ans ^= last;
break;
}
}
write(ans);
putchar('\n');
return 0;
}
感谢支持!
【模板】Splay(伸展树)普通平衡树(数据加强版)/洛谷P6136的更多相关文章
- 【学时总结】◆学时·VI◆ SPLAY伸展树
◆学时·VI◆ SPLAY伸展树 平衡树之多,学之不尽也…… ◇算法概述 二叉排序树的一种,自动平衡,由 Tarjan 提出并实现.得名于特有的 Splay 操作. Splay操作:将节点u通过单旋. ...
- Splay伸展树学习笔记
Splay伸展树 有篇Splay入门必看文章 —— CSDN链接 经典引文 空间效率:O(n) 时间效率:O(log n)插入.查找.删除 创造者:Daniel Sleator 和 Robert Ta ...
- Splay伸展树入门(单点操作,区间维护)附例题模板
Pps:终于学会了伸展树的区间操作,做一个完整的总结,总结一下自己的伸展树的单点操作和区间维护,顺便给未来的自己总结复习用. splay是一种平衡树,[平均]操作复杂度O(nlogn).首先平衡树先是 ...
- [Splay伸展树]splay树入门级教程
首先声明,本教程的对象是完全没有接触过splay的OIer,大牛请右上角.. 首先引入一下splay的概念,他的中文名是伸展树,意思差不多就是可以随意翻转的二叉树 PS:百度百科中伸展树读作:BoGa ...
- Splay 伸展树
废话不说,有篇论文可供参考:杨思雨:<伸展树的基本操作与应用> Splay的好处可以快速分裂和合并. ===============================14.07.26更新== ...
- Codeforces 675D Tree Construction Splay伸展树
链接:https://codeforces.com/problemset/problem/675/D 题意: 给一个二叉搜索树,一开始为空,不断插入数字,每次插入之后,询问他的父亲节点的权值 题解: ...
- UVA 11922 Permutation Transformer —— splay伸展树
题意:根据m条指令改变排列1 2 3 4 … n ,每条指令(a, b)表示取出第a~b个元素,反转后添加到排列尾部 分析:用一个可分裂合并的序列来表示整个序列,截取一段可以用两次分裂一次合并实现,粘 ...
- [算法] 数据结构 splay(伸展树)解析
前言 splay学了已经很久了,只不过一直没有总结,鸽了好久来写一篇总结. 先介绍 splay:亦称伸展树,为二叉搜索树的一种,部分操作能在 \(O( \log n)\) 内完成,如插入.查找.删除. ...
- 线段树入门详解,洛谷P3372 【模板】线段树 1
关于线段树: 本随笔参考例题 P3372 [模板]线段树 1 所谓线段树就是把一串数组拆分成一个一个线段形成的一棵树. 比如说像这样的一个数组1,2,3,4,5: 1 ~ 5 / ...
随机推荐
- 零基础入门stm32基本定时器详解
一.基本定时器介绍 在STM32中,基本定时器有TIM6.TIM7等.基本定时器主要包含时基单元,提供16位的计数,能计数0~65535.基本定时器除了计数功能以外,还能输出给DAC模块一个TRGO信 ...
- STM32核心板焊接
焊接工具和材料 电烙铁及烙铁头介绍 电烙铁有很多种,常用的有内热式.外热式.恒温式和吸锡式,为了方便携带,建议使内热式电烙铁,且要带烙铁架和海绵,烙铁架用于放置电烙铁,海绵用于擦拭烙铁锡渣,因此海绵不 ...
- JVM:垃圾收集器与对象的"存活"问题
垃圾收集器垃圾收集(Garbage Collection,GC).当需要排查各种内存溢出.内存泄露问题时,当垃圾收集成为系统更高并发量的瓶颈时,我们需要去了解GC和内存分配. 检查对象的"存 ...
- c++继承关系中成员函数的重载、重写、重定义之间的区别
1.Override.Overload.Redefine Overload 重载只能发生在类内部,不能发生在子类和父类的继承中.具体来说,如果子类中有父类同名.同返回值类型,但是不同参数列表,这两个在 ...
- 实验 1: SDN拓扑实践
(图片和文档是自己写的,因为在CSDN也写了,所以会有自己的水印) 一.实验目的 能够使用源码安装Mininet: 能够使用Mininet的可视化工具生成拓扑: 能够使用Mininet的命令行生成特定 ...
- Shadertoy 教程 Part 4 - 绘制多个2D图形和混入
Note: This series blog was translated from Nathan Vaughn's Shaders Language Tutorial and has been au ...
- Spring事务不生效问题
事务未生效可能造成严重的数据不一致性问题,因而保证事务生效至关重要.Spring事务是通过Spring aop实现的,所以不生效的本质问题是spring aop没生效,或者说没有代理成功,所以有必要了 ...
- 应对gitee容量超限. 保留star/fork/评论
应对gitee容量超限 进入企业版,"管理"-"仓库管理",点"清空仓库". 在E:\gitee目录上右击,"git bash h ...
- Memory Analyzer Tool 使用
转载出处:https://wensong.iteye.com/blog/1986449 最近一段时间一直在研究热部署,热部署中涉及到一个比较头痛的问题就是查内存泄露(Memory Leak),于是乎在 ...
- windows端口占用处理方法
(1)输入命令:netstat -ano,列出所有端口的情况.在列表中我们观察被占用的端口,比如是8081,首先找到它.C:\Users\Administrator>netstat -ano活动 ...