题目描述
您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:
- 插入 xx 数
- 删除 xx 数(若有多个相同的数,因只删除一个)
- 查询 xx 数的排名(排名定义为比当前数小的数的个数 +1 。若有多个相同的数,因输出最小的排名)
- 查询排名为 xx 的数
- 求 xx 的前驱(前驱定义为小于 xx ,且最大的数)
- 求 xx 的后继(后继定义为大于 xx ,且最小的数)
输入输出格式
输入格式:
第一行为 nn ,表示操作的个数,下面 nn 行每行有两个数 optopt 和 xx , optopt 表示操作的序号( 1 \leq opt \leq 61≤opt≤6 )
输出格式:
对于操作 3,4,5,63,4,5,6 每行输出一个数,表示对应答案
输入输出样例
输入样例#1:
10
1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598
输出样例#1:
106465
84185
492737
说明
时空限制:1000ms,128M
1.n的数据范围: n \leq 100000n≤100000
2.每个数的数据范围: [-{10}^7, {10}^7][−107,107]
学习了下伸展树,伸展树的功能真的很强大,直接粘板子就行,板子里的注释写的挺详细了
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
struct tree
{
int father,lch,rch,val,cnt,size; //父节点、左儿子、右儿子、值、该值出现次数、子树大小
}stree[1000005];
int root,tot; // 根节点,树的大小
inline void clearr(int x)
{
stree[x].lch = stree[x].rch = stree[x].father = stree[x].size = stree[x].cnt = stree[x].val = 0;
}
inline bool get(int x) // 判断是父亲的左儿子还是右儿子
{
return stree[ stree[x].father ].rch == x;
}
inline void update(int x) // 更新子树大小
{
if (x)
{
stree[x].size = stree[x].cnt; // 该值出现次数 + 左右子树大小
if (stree[x].lch)
stree[x].size += stree[ stree[x].lch ].size;
if (stree[x].rch)
stree[x].size += stree[ stree[x].rch ].size;
}
}
inline void rotate(int x)//旋转
{
int fa,oldf,whichx;
fa = stree[x].father; // x的父节点
oldf = stree[fa].father; // x父节点的父节点
whichx = get(x); // 判断x是父节点左儿子还是右儿子
if (whichx) //x是父节点的右儿子
{
stree[fa].rch = stree[x].lch;
stree[ stree[fa].rch ].father = fa; // 将x的左儿子变为x父节点的右儿子
stree[x].lch = fa;
stree[fa].father = x;
stree[x].father = oldf; // 将x父节点变为x左儿子
}
else // 同上
{
stree[fa].lch = stree[x].rch;
stree[ stree[fa].lch ].father = fa;
stree[x].rch = fa;
stree[fa].father = x;
stree[x].father = oldf;
}
if (oldf) // 如果有x的父节点有父节点,则更新祖父节点的(左/右)儿子为x
{
if (stree[oldf].rch == fa)
stree[oldf].rch = x;
else
stree[oldf].lch = x;
}
update(fa);
update(x); // 更新x与x父节点的大小
}
inline void splay(int x, int t)//将x伸展到t的子节点
{
int fa = stree[x].father; //将x伸展至父节点
while (fa != t)
{
if (stree[fa].father) // 如果父节点有父节点
{
if (get(x) == get(fa)) // 一字型(需旋转父节点再旋转x)
rotate(fa);
else
rotate(x); // 之字形(需旋转x两次)
}
rotate(x); // 旋转x
fa = stree[x].father; //更新父节点
}
if (!t)
root = x; // 更新根节点
}
inline void insert(int x) // 插入x
{
if (!root) // 如果splay树为空
{
tot++;
stree[tot].lch = stree[tot].rch = stree[tot].father = 0;
root = tot;
stree[tot].size = stree[tot].cnt = 1;
stree[tot].val = x;
return;
}
int u = root;
int fa = 0;
while (1)
{
if (x == stree[u].val) // 如果splay树里已经有这个值
{
stree[u].cnt++;
update(u);
update(fa);
splay(u, 0);
return;
}
fa = u;
if (stree[u].val < x)
u = stree[u].rch;
else
u = stree[u].lch; // 找该值应该是在当前节点的左/右子树内
if (!u) // 如果找到x应作为fa节点的左/右儿子
{
tot++; // 树大小+1
stree[tot].lch = stree[tot].rch = 0;
stree[tot].father = fa;
stree[tot].size = stree[tot].cnt = 1;
if (stree[fa].val < x)
stree[fa].rch = tot;
else
stree[fa].lch = tot;
stree[tot].val = x; // 创建该节点
update(fa); // 更新父节点大小
splay(tot, 0); // 将当前节点伸展到根
return;
}
}
}
int find(int x) //查找x为第几小的值
{
int ans = 0;
int u = root;
while (1)
{
if (x < stree[u].val) // 如果x在当前节点的左子树内
u = stree[u].lch;
else // 如果x在当前节点的右子树内
{
if (stree[u].lch) //当前节点的左子树都比x小
ans += stree[ stree[u].lch ].size;
if (x == stree[u].val) // 如果x是当前节点,则找到排名
{
splay(u, 0); // 将x伸展到根节点
return ans + 1;
}
ans += stree[u].cnt; // 当前节点比x小
u = stree[u].rch; // 更新当前节点
}
}
}
int findx(int x)//找第x小的点
{
int u = root;
while (1)
{
if (stree[u].lch && stree[ stree[u].lch ].size >= x) // 如果当前节点的左子树大小已经大于等于x,即就在当前节点的左子树内找
u = stree[u].lch;
else
{
int tmp = stree[u].cnt;
if (stree[u].lch)
tmp += stree[ stree[u].lch ].size;
if (tmp >= x) // 当前值即为要寻找的值
return stree[u].val;
x -= tmp; // 已经找到最小的tmp个树
u = stree[u].rch; // 去当前节点的有子树内找
}
}
}
inline int pre()//查找x的前驱(需先插入x,查找完后删除,返回值为前驱的下标)
{
int now = stree[root].lch; //插入x时,x已经作为树的根,在x的左子树内找最大值
while (stree[now].rch)
now = stree[now].rch;
return now;
}
inline int next()//查找x的后继(需先插入x,查找完后删除,返回值为后继的下标)
{
int now = stree[root].rch; //插入x时,x已经作为树的根,在x的右子树内找最小值
while (stree[now].lch)
now = stree[now].lch;
return now;
}
void deletee(int x) // 删除x
{
p = find(x); // 此时x已经成为根节点
if (stree[root].cnt > 1) // 如果x出现次数超过一次
{
stree[root].cnt--;
update(root);
return;
}
if (!stree[root].lch && !stree[root].rch) // 如果x没有左右子树,直接删除即可
{
clearr(root);
root = 0;
return;
}
if (!stree[root].lch) //如果x没有左子树,则将x的右儿子作为根,直接删除x
{
int tmp = root;
root = stree[root].rch;
stree[root].father = 0;
clearr(tmp);
return;
}
if (!stree[root].rch)//如果x没有右子树,则将x的左儿子作为根,直接删除x
{
int tmp = root;
root = stree[root].lch;
stree[root].father = 0;
clearr(tmp);
return;
}
int left = pre(); // 找x的前驱
int tmp = root;
splay(left, 0); // 将x的前驱伸展到根节点
stree[root].rch = stree[tmp].rch;
stree[ stree[tmp].rch ].father = root; // x的右儿子作为x前驱的右儿子
clearr(tmp); // 删除x节点
update(root); // 更新树大小
return;
}
int main()
{
// freopen("in.txt", "r", stdin);
int n;
scanf("%d", &n);
while (n--)
{
int f,x;
scanf("%d%d", &f, &x);
if (f == 1)
insert(x);
if (f == 2)
deletee(x);
if (f == 3)
printf("%d\n", find(x));
if (f == 4)
printf("%d\n", findx(x));
if (f == 5)
{
insert(x);
printf("%d\n", stree[pre()].val);
deletee(x);
}
if (f == 6)
{
insert(x);
printf("%d\n", stree[next()].val);
deletee(x);
}
}
return 0;
}