splay(伸展树)
题目一
(https://www.luogu.com.cn/problem/P3369)
题目描述
您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:
插入 x 数
删除 x 数(若有多个相同的数,因只删除一个)
查询 x 数的排名(排名定义为比当前数小的数的个数 +1 )
查询排名为 x 的数
求 x 的前驱(前驱定义为小于 x,且最大的数)
求 x 的后继(后继定义为大于 x,且最小的数)
输入格式
第一行为 n,表示操作的个数,下面 nn 行每行有两个数 opt 和 x,opt 表示操作的序号1≤opt≤6
输出格式
对于操作 3,4,5,63,4,5,6 每行输出一个数,表示对应答案
输入
10
1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598
输出
106465
84185
492737
说明/提示
【数据范围】
对于 100% 的数据,1≤n≤10,∣x∣≤10^7
来源:Tyvj1728 原名:普通平衡树
在此鸣谢
思路
插入了-inf和inf。
#include <bits/stdc++.h>
using namespace std;
const int N = 200005;
int ch[N][2], ff[N], val[N], cnt[N], size[N], ncnt, root;
bool chk(int x) {
return ch[ff[x]][1] == x;
}
void pushup(int x) {
size[x] = size[ch[x][0]] + size[ch[x][1]] + cnt[x];
}
void rotate(int x) {
int y = ff[x], z = ff[y], k = chk(x), w = ch[x][k^1];
ch[y][k] = w;
ff[w] = y;
ch[z][chk(y)] = x;
ff[x] = z;
ch[x][k^1] = y;
ff[y] = x;
pushup(y);
pushup(x);
}
void splay(int x, int goal = 0) {
while (ff[x] != goal) {
int y = ff[x], z = ff[y];
if (z != goal) {
if (chk(x) == chk(y))
rotate(y);
else
rotate(x);
}
rotate(x);
}
if (!goal)
root = x;
}
void insert(int x) {//添加
int cur = root, p = 0;
while (cur && val[cur] != x) {
p = cur;
cur = ch[cur][x > val[cur]];
}
if (cur) {
cnt[cur]++;
} else {
cur = ++ncnt;
if (p)
ch[p][x > val[p]] = cur;
ch[cur][0] = ch[cur][1] = 0;
ff[cur] = p;
val[cur] = x;
cnt[cur] = size[cur] = 1;
}
splay(cur);
}
void find(int x) {//查找排名为k的点的位置
int cur = root;
while (ch[cur][x > val[cur]] && x != val[cur]) {
cur = ch[cur][x > val[cur]];
}
splay(cur);
}
int rak(int x) { //x的排名查询
find(x);
if(val[root]!=x) { //x不存在返回-1
return -1;
}
return size[ch[root][0]];
}
int kth(int k, int f) {//查找排名为k的点的位置 f=0第k小 f=0第k大
int siz=rak(0x3f3f3f3f)-1;
if(k<1||siz<k) {//不存在排名为k的点返回-1
return -1;
}
k=f?siz+2-k:k+1;
int cur = root;
while (1) {
if (ch[cur][0] && k <= size[ch[cur][0]]) {
cur = ch[cur][0];
} else if (k > size[ch[cur][0]] + cnt[cur]) {
k -= size[ch[cur][0]] + cnt[cur];
cur = ch[cur][1];
} else {
splay(cur);
return cur;
}
}
}
int pre(int x) {//前驱
find(x);
if (val[root] < x)
return root;
int cur = ch[root][0];
while (ch[cur][1])
cur = ch[cur][1];
splay(cur);
return cur;
}
int succ(int x) {//后缀
find(x);
if (val[root] > x)
return root;
int cur = ch[root][1];
while (ch[cur][0])
cur = ch[cur][0];
splay(cur);
return cur;
}
void remove(int x) {//删除
int last = pre(x), next = succ(x);
splay(last);
splay(next, last);
int del = ch[next][0];
if (cnt[del] > 1) {
cnt[del]--;
splay(del);
} else
ch[next][0] = 0;
pushup(next), pushup(root);
}
int n, op, x;
int main() {
scanf("%d", &n);
insert(0x3f3f3f3f);
insert(0xcfcfcfcf);
while (n--) {
scanf("%d%d", &op, &x);
switch (op) {
case 1:
insert(x);
break;
case 2:
remove(x);
break;
case 3: {
printf("%d\n", rak(x));
break;
}
case 4: {
int u=kth(x, 0);//第x小
if(u==-1) printf("-1\n");
else printf("%d\n", val[u]);
break;
};
case 5: {
printf("%d\n", val[pre(x)]);
break;
}
case 6: {
printf("%d\n", val[succ(x)]);
break;
}
}
}
}
题目二
(https://www.luogu.com.cn/problem/P1486)
题目描述
OIER 公司是一家大型专业化软件公司,有着数以万计的员工。作为一名出纳员,我的任务之一便是统计每位员工的工资。这本来是一份不错的工作,但是令人郁闷的是,我们的老板反复无常,经常调整员工的工资。如果他心情好,就可能把每位员工的工资加上一个相同的量。反之,如果心情不好,就可能把当前在公司的所有员工的工资扣除一个相同的量。我真不知道除了调工资他还做什么其它事情。
工资的频繁调整很让员工反感,尤其是集体扣除工资的时候,一旦某位员工发现自己的工资已经低于了合同规定的工资下界,他就会立刻气愤地离开公司,并且再也不会回来了。每位员工的工资下界都是统一规定的。每当一个人离开公司,我就要从电脑中把他的工资档案删去,同样,每当公司招聘了一位新员工,我就得为他新建一个工资档案。
老板经常到我这边来询问工资情况,他并不问具体某位员工的工资情况,而是问现在工资第 k 多的员工拿多少工资。每当这时,我就不得不对数万个员工进行一次漫长的排序,然后告诉他答案。
好了,现在你已经对我的工作了解不少了。正如你猜的那样,我想请你编一个工资统计程序。怎么样,不是很困难吧?
如果某个员工的初始工资低于最低工资标准,那么将不计入最后的答案内。
输入格式
第一行有两个整数 n 和 min。n 表示下面有多少条命令,min 表示工资下界。
接下来的 n 行,每行一个字符 x 和一个整数 k,表示一条命令。命令可以是以下四种之一:
I k 新建一个工资档案,初始工资为 k。如果某员工的初始工资低于工资下界,他将立刻离开公司。
A k 把每位员工的工资加上 k 。
S k 把每位员工的工资扣除 k。
F k 查询第 k 多的工资。
在初始时,可以认为公司里一个员工也没有。
输出格式
对于每条 F 命令,你的程序要输出一行,仅包含一个整数,为当前工资第 k 多的员工所拿的工资数,如果 kk 大于目前员工的数目,则输出 -1。
输出的最后一行包含一个整数,为离开公司的员工的总数。
请注意,初始工资低于工资下界的员工不算做离开公司的员工。
输入
9 10
I 60
I 70
S 50
F 2
I 30
S 15
A 5
F 1
F 2
输出
10
20
-1
2
说明/提示
数据规模与约定
对于全部的测试点,保证:
I 命令的条数不超过 10^5;
A 和 S 命令的总条数不超过 100;
F 命令的条数不超过 10^5;
每次工资调整的调整量不超过 10^3;
新员工的工资不超过10^5
0≤n≤3×10^5
0≤min≤10^9
输入的所有数字均在 32 位带符号整形范围内。
思路
对于+ - 工资, 我们用t记录一下就可以了。
那么实际上的工资是val+t。
插入时, 插入x-t
x+t<min
x<min-t
那么把min-t插入树,作为根,那么根的左子树就可以删除了。记得最后把min-t删除。
删除的节点树=总I命令-剩余的节点数量, max_inf的排名-1(减去min_inf)。
#include <bits/stdc++.h>
using namespace std;
const int N = 200005;
const int maxinf=1e9+5;
const int mininf=-maxinf;
int ch[N][2], ff[N], val[N], cnt[N], size[N], ncnt, root;
bool chk(int x) {
return ch[ff[x]][1] == x;
}
void pushup(int x) {
size[x] = size[ch[x][0]] + size[ch[x][1]] + cnt[x];
}
void rotate(int x) {
int y = ff[x], z = ff[y], k = chk(x), w = ch[x][k^1];
ch[y][k] = w;
ff[w] = y;
ch[z][chk(y)] = x;
ff[x] = z;
ch[x][k^1] = y;
ff[y] = x;
pushup(y);
pushup(x);
}
void splay(int x, int goal = 0) {
while (ff[x] != goal) {
int y = ff[x], z = ff[y];
if (z != goal) {
if (chk(x) == chk(y))
rotate(y);
else
rotate(x);
}
rotate(x);
}
if (!goal)
root = x;
}
void insert(int x) {//添加
int cur = root, p = 0;
while (cur && val[cur] != x) {
p = cur;
cur = ch[cur][x > val[cur]];
}
if (cur) {
cnt[cur]++;
} else {
cur = ++ncnt;
if (p)
ch[p][x > val[p]] = cur;
ch[cur][0] = ch[cur][1] = 0;
ff[cur] = p;
val[cur] = x;
cnt[cur] = size[cur] = 1;
}
splay(cur);
}
int find(int x) {
int cur = root;
while (ch[cur][x > val[cur]] && x != val[cur]) {
cur = ch[cur][x > val[cur]];
}
splay(cur);
return cur;
}
int rak(int x) { //x的排名查询
find(x);
if(val[root]!=x) { //x不存在返回-1
return -1;
}
return size[ch[root][0]];
}
int kth(int k, int f) {//查找排名为k的点的位置 f=0第k小 f=0第k大
int siz=rak(max_inf)-1;
if(k<1||siz<k) {//不存在排名为k的点返回-1
return -1;
}
k=f?siz+2-k:k+1;
int cur = root;
while (1) {
if (ch[cur][0] && k <= size[ch[cur][0]]) {
cur = ch[cur][0];
} else if (k > size[ch[cur][0]] + cnt[cur]) {
k -= size[ch[cur][0]] + cnt[cur];
cur = ch[cur][1];
} else {
splay(cur);
return cur;
}
}
}
int pre(int x) {//前驱
find(x);
if (val[root] < x)
return root;
int cur = ch[root][0];
while (ch[cur][1])
cur = ch[cur][1];
splay(cur);
return cur;
}
int succ(int x) {//后缀
find(x);
if (val[root] > x)
return root;
int cur = ch[root][1];
while (ch[cur][0])
cur = ch[cur][0];
splay(cur);
return cur;
}
void del(int x) {//删除
int last = pre(x), next = succ(x);
splay(last);
splay(next, last);
int del = ch[next][0];
if (cnt[del] > 1) {
cnt[del]--;
splay(del);
} else
ch[next][0] = 0;
pushup(next), pushup(root);
}
int main() {
insert(max_inf);
insert(min_inf);
int n, m, t=0, sum=0;
scanf("%d%d", &n, &m);
while(n--){
char s[5]; int x; scanf("%s%d", s, &x);
if(s[0]=='I'){
if(x<m) continue;
insert(x-t);
sum++;
}
else if(s[0]=='A'){
t+=x;
}
else if(s[0]=='S'){
t-=x;
insert(m-t);
int a=find(min_inf), b=find(m-t);
splay(a, 0);
splay(b, a);
ch[ch[root][1]][0]=0;
del(m-t);
}
else if(s[0]=='F'){
int u=kth(x, 1);
if(u==-1) printf("-1\n");
else printf("%d\n", val[u]+t);
}
}
printf("%d\n", sum-(rak(max_inf)-1));
return 0;
}
题目三
(https://www.luogu.com.cn/problem/P2234)
题目描述
Tiger最近被公司升任为营业部经理,他上任后接受公司交给的第一项任务便是统计并分析公司成立以来的营业情况。
Tiger拿出了公司的账本,账本上记录了公司成立以来每天的营业额。分析营业情况是一项相当复杂的工作。由于节假日,大减价或者是其他情况的时候,营业额会出现一定的波动,当然一定的波动是能够接受的,但是在某些时候营业额突变得很高或是很低,这就证明公司此时的经营状况出现了问题。经济管理学上定义了一种最小波动值来衡量这种情况:
当最小波动值越大时,就说明营业情况越不稳定。
而分析整个公司的从成立到现在营业情况是否稳定,只需要把每一天的最小波动值加起来就可以了。你的任务就是编写一个程序帮助Tiger来计算这一个值。
第一天的最小波动值为第一天的营业额。
该天的最小波动值=min{|该天以前某一天的营业额-该天营业额|}。
输入格式
输入由文件’turnover.in’读入。
第一行为正整数n(n<=32767) ,表示该公司从成立一直到现在的天数,接下来的n行每行有一个整数ai(|ai|<=1000000) ,表示第i天公司的营业额,可能存在负数。
输出格式
输入
6
5
1
2
5
4
6
输出
12
说明/提示
结果说明:5+|1-5|+|2-1|+|5-5|+|4-5|+|6-5|=5+4+1+0+1+1=12
思路
比较简单
每次插入,查询就询问根节点的val的数量>1。就是0
否则距离x最近的就是前驱和后驱
#include <bits/stdc++.h>
using namespace std;
const int N = 200005;
const int maxinf=1e9+5;
const int mininf=-maxinf;
int ch[N][2], ff[N], val[N], cnt[N], size[N], ncnt, root;
bool chk(int x) {
return ch[ff[x]][1] == x;
}
void pushup(int x) {
size[x] = size[ch[x][0]] + size[ch[x][1]] + cnt[x];
}
void rotate(int x) {
int y = ff[x], z = ff[y], k = chk(x), w = ch[x][k^1];
ch[y][k] = w;
ff[w] = y;
ch[z][chk(y)] = x;
ff[x] = z;
ch[x][k^1] = y;
ff[y] = x;
pushup(y);
pushup(x);
}
void splay(int x, int goal = 0) {
while (ff[x] != goal) {
int y = ff[x], z = ff[y];
if (z != goal) {
if (chk(x) == chk(y))
rotate(y);
else
rotate(x);
}
rotate(x);
}
if (!goal)
root = x;
}
void insert(int x) {//添加
int cur = root, p = 0;
while (cur && val[cur] != x) {
p = cur;
cur = ch[cur][x > val[cur]];
}
if (cur) {
cnt[cur]++;
} else {
cur = ++ncnt;
if (p)
ch[p][x > val[p]] = cur;
ch[cur][0] = ch[cur][1] = 0;
ff[cur] = p;
val[cur] = x;
cnt[cur] = size[cur] = 1;
}
splay(cur);
}
int find(int x) {
int cur = root;
while (ch[cur][x > val[cur]] && x != val[cur]) {
cur = ch[cur][x > val[cur]];
}
splay(cur);
return cur;
}
int rak(int x) { //x的排名查询
find(x);
if(val[root]!=x) { //x不存在返回-1
return -1;
}
return size[ch[root][0]];
}
int kth(int k, int f) {//查找排名为k的点的位置 f=0第k小 f=0第k大
int siz=rak(max_inf)-1;
if(k<1||siz<k) {//不存在排名为k的点返回-1
return -1;
}
k=f?siz+2-k:k+1;
int cur = root;
while (1) {
if (ch[cur][0] && k <= size[ch[cur][0]]) {
cur = ch[cur][0];
} else if (k > size[ch[cur][0]] + cnt[cur]) {
k -= size[ch[cur][0]] + cnt[cur];
cur = ch[cur][1];
} else {
splay(cur);
return cur;
}
}
}
int pre(int x) {//前驱
find(x);
if (val[root] < x)
return root;
int cur = ch[root][0];
while (ch[cur][1])
cur = ch[cur][1];
splay(cur);
return cur;
}
int nxt(int x) {//后缀
find(x);
if (val[root] > x)
return root;
int cur = ch[root][1];
while (ch[cur][0])
cur = ch[cur][0];
splay(cur);
return cur;
}
void del(int x) {//删除
int last = pre(x), next = nxt(x);
splay(last);
splay(next, last);
int del = ch[next][0];
if (cnt[del] > 1) {
cnt[del]--;
splay(del);
} else
ch[next][0] = 0;
pushup(next), pushup(root);
}
int getans(int x){
find(x);
if(cnt[root]>1) return 0;
int ans=1<<30;
int lson=pre(x);
int rson=nxt(x);
ans=min(ans, abs(val[lson]-x));
ans=min(ans, abs(val[rson]-x));
return ans;
}
int main() {
insert(max_inf);
insert(min_inf);
int n, ans=0; scanf("%d", &n);
for(int i=1; i<=n; i++){
int x; scanf("%d", &x);
if(i==1){
insert(x);
ans=x;
}
else{
insert(x);
ans+=getans(x);
}
}
printf("%d\n", ans);
return 0;
}
题目四
(https://www.luogu.com.cn/problem/P1198)
题目描述
现在请求你维护一个数列,要求提供以下两种操作:
1、 查询操作。
语法:Q L
功能:查询当前数列中末尾L个数中的最大的数,并输出这个数的值。
限制:LL不超过当前数列的长度。(L>0)
2、 插入操作。
语法:A n
功能:将n加上t,其中tt是最近一次查询操作的答案(如果还未执行过查询操作,则t=0),并将所得结果对一个固定的常数D取模,将所得答案插入到数列的末尾。
限制:n是整数(可能为负数)并且在长整范围内。
注意:初始时数列是空的,没有一个数。
输入格式
第一行两个整数,M 和 D,其中 M 表示操作的个数,D 如上文中所述。
接下来的 M 行,每行一个字符串,描述一个具体的操作。语法如上文所述。
输出格式
对于每一个查询操作,你应该按照顺序依次输出结果,每个结果占一行。
输入
5 100
A 96
Q 1
A 97
Q 1
Q 2
输出
96
93
96
说明/提示
数据规模与约定
对于全部的测试点,保证1≤M≤2×105。1≤D≤2×109
思路
把下标作为val。并且分配的点就是下标。询问应该最多中。每次把ncnt-x旋转到根。那么右子树的mx就是答案。
#include <bits/stdc++.h>
using namespace std;
const int N = 200005;
const int maxinf=1e9+5;
const int mininf=-maxinf;
int ch[N][2], ff[N], val[N], cnt[N], size[N], ncnt, root;
int mx[N], w[N];
bool chk(int x) {
return ch[ff[x]][1] == x;
}
void pushup(int x) {
size[x] = size[ch[x][0]] + size[ch[x][1]] + cnt[x];
mx[x]=w[x];
int lc=ch[x][0], rc=ch[x][1];
if(lc){
mx[x]=max(mx[x], mx[lc]);
}
if(rc){
mx[x]=max(mx[x], mx[rc]);
}
}
void rotate(int x) {
int y = ff[x], z = ff[y], k = chk(x), w = ch[x][k^1];
ch[y][k] = w;
ff[w] = y;
ch[z][chk(y)] = x;
ff[x] = z;
ch[x][k^1] = y;
ff[y] = x;
pushup(y);
pushup(x);
}
void splay(int x, int goal = 0) {
while (ff[x] != goal) {
int y = ff[x], z = ff[y];
if (z != goal) {
if (chk(x) == chk(y))
rotate(y);
else
rotate(x);
}
rotate(x);
}
if (!goal)
root = x;
}
void insert(int x, int v) {//添加
int cur = root, p = 0;
while (cur && val[cur] != x) {
p = cur;
cur = ch[cur][x > val[cur]];
}
if (cur) {
cnt[cur]++;
} else {
cur = ++ncnt;
if (p)
ch[p][x > val[p]] = cur;
ch[cur][0] = ch[cur][1] = 0;
ff[cur] = p;
val[cur] = x;
w[cur]=mx[cur]=v;
cnt[cur] = size[cur] = 1;
}
splay(cur);
}
int main() {
insert(1, min_inf);
int n, d, t=0; scanf("%d%d", &n, &d);
for(int i=1; i<=n; i++){
char op[5]; int x; scanf("%s%d", op, &x);
if(op[0]=='A'){
x=(x+t)%d;
insert(ncnt+1, x);
}
else{
splay(ncnt-x);
t=mx[ch[root][1]];
printf("%d\n", t);
}
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)