题目链接

https://www.luogu.com.cn/problem/P6136

题目大意

需要写一种数据结构,来维护一些非负整数( \(int\) 范围内)的升序序列,其中需要提供以下操作:

  1. 插入一个整数 \(x\) 。
  2. 删除一个整数 \(x\) (若有多个相同的数,只删除一个)。
  3. 查询整数 \(x\) 的排名(排名定义为比当前数小的数的个数 \(+1\) )。
  4. 查询排名为 \(x\) 的数(如果不存在,则认为是排名小于 \(x\) 的最大数。保证 \(x\) 不会超过当前数据结构中数的总数)。
  5. 求 \(x\) 的前驱(前驱定义为小于 \(x\) ,且最大的数)。
  6. 求 \(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的更多相关文章

  1. 【学时总结】◆学时·VI◆ SPLAY伸展树

    ◆学时·VI◆ SPLAY伸展树 平衡树之多,学之不尽也…… ◇算法概述 二叉排序树的一种,自动平衡,由 Tarjan 提出并实现.得名于特有的 Splay 操作. Splay操作:将节点u通过单旋. ...

  2. Splay伸展树学习笔记

    Splay伸展树 有篇Splay入门必看文章 —— CSDN链接 经典引文 空间效率:O(n) 时间效率:O(log n)插入.查找.删除 创造者:Daniel Sleator 和 Robert Ta ...

  3. Splay伸展树入门(单点操作,区间维护)附例题模板

    Pps:终于学会了伸展树的区间操作,做一个完整的总结,总结一下自己的伸展树的单点操作和区间维护,顺便给未来的自己总结复习用. splay是一种平衡树,[平均]操作复杂度O(nlogn).首先平衡树先是 ...

  4. [Splay伸展树]splay树入门级教程

    首先声明,本教程的对象是完全没有接触过splay的OIer,大牛请右上角.. 首先引入一下splay的概念,他的中文名是伸展树,意思差不多就是可以随意翻转的二叉树 PS:百度百科中伸展树读作:BoGa ...

  5. Splay 伸展树

    废话不说,有篇论文可供参考:杨思雨:<伸展树的基本操作与应用> Splay的好处可以快速分裂和合并. ===============================14.07.26更新== ...

  6. Codeforces 675D Tree Construction Splay伸展树

    链接:https://codeforces.com/problemset/problem/675/D 题意: 给一个二叉搜索树,一开始为空,不断插入数字,每次插入之后,询问他的父亲节点的权值 题解: ...

  7. UVA 11922 Permutation Transformer —— splay伸展树

    题意:根据m条指令改变排列1 2 3 4 … n ,每条指令(a, b)表示取出第a~b个元素,反转后添加到排列尾部 分析:用一个可分裂合并的序列来表示整个序列,截取一段可以用两次分裂一次合并实现,粘 ...

  8. [算法] 数据结构 splay(伸展树)解析

    前言 splay学了已经很久了,只不过一直没有总结,鸽了好久来写一篇总结. 先介绍 splay:亦称伸展树,为二叉搜索树的一种,部分操作能在 \(O( \log n)\) 内完成,如插入.查找.删除. ...

  9. 线段树入门详解,洛谷P3372 【模板】线段树 1

    关于线段树: 本随笔参考例题      P3372 [模板]线段树 1 所谓线段树就是把一串数组拆分成一个一个线段形成的一棵树. 比如说像这样的一个数组1,2,3,4,5: 1 ~ 5 /       ...

随机推荐

  1. InitSpatialMetaData()速度慢的问题

    解决方法:with sqlite3.connect(dbfile) as con: con.enable_load_extension(True) con.execute("SELECT l ...

  2. 8M的摄像头,30fps摄像时,60hz的LCD刷新频率,请问camera每秒向BB传输多少数据,如何计算

    8M的摄像头,30fps摄像时,60hz的LCD刷新频率,请问camera每秒向BB传输多少数据,如何计算 xiang2012 Post at 2012/8/7 10:37:33 8M的摄像头,30f ...

  3. MyBatis源码分析(三):MyBatis初始化(配置文件读取和解析)

    一. 介绍MyBatis初始化过程 项目是简单的Mybatis应用,编写SQL Mapper,还有编写的SqlSessionFactoryUtil里面用了Mybatis的IO包里面的Resources ...

  4. linux 内核源代码情景分析——linux 内核源码中的汇编语言代码

    1. 用汇编语言编写部分核心代码的原因: ① 操作系统内核中的底层程序直接与硬件打交道,需要用到一些专用的指令,而这些指令在C语言中并无对应的语言成分: ② CPU中的一些特殊指令也没有对应的C语言成 ...

  5. 记录 mysql 使用时遇到的问题

    1,linux平台上mysqld和mysql的区别. 首先,mysql数据库是标准的c/s架构,yum安装时注意到了,有mysql和mysql-server包 mysql是客户端的工具,mysqld ...

  6. zabbix部署文档

    环境:zabbix server centos 7 1611最小化安装 172.16.103.2 zabbix client Centos 7 1611 最小化安装 172.16.103.3 1,配置 ...

  7. selenium2.x 与 selenium3.x 最大区别

    一.selenium2.x 与 selenium3.x 最大区别 (1) 从3.0版本selenium开始使用火狐浏览器完成web自动化就需要用到驱动包了. (2) 而2.0版本的selenium使用 ...

  8. Webshell 一句话木马

    Webshell介绍 什么是 WebShell webshell就是以asp.php.jsp或者cgj等网页文件形式存在的一种命令执行环境,也可以将其称做为一种网页后门 由于 webshell其大多是 ...

  9. IDEA中Update resources和Update classes and resources、Redeploy、Restart server的区别

    选项 描述 update resources 所有更改的资源都会更新(HTML,JSP,JavaScript,CSS和图像文件) update classes and resources 更改的资源将 ...

  10. Part 16 ng include directive in AngularJS

    ng-include directive is used to embed an HTML page into another HTML page. This technique is extreme ...