题目描述
您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:
- xx 数
- xx 数(若有多个相同的数,因只删除一个)
- xx 数的排名(排名定义为比当前数小的数的个数 +1+1 )
- 排名为 xx 的数
- xx 的前驱(前驱定义为小于 xx,且最大的数)
- xx 的后继(后继定义为大于 xx,且最小的数)
输入格式
第一行为 nn,表示操作的个数,下面 nn 行每行有两个数 opt 和 x,opt 表示操作的序号(1<=opt<=6),
输出格式
对于操作 3,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%100% 的数据,
,
,
来源:Tyvj1728 原名:普通平衡树
在此鸣谢
附上一个大佬的B站链接:
https://www.bilibili.com/video/av60140850?from=search&seid=17315401495564564850
看题目我们知道这是一道fhq-treap的模板题,当然treap树也能做,首先我们来讲fhq-treap(无旋treap)叭。
顾名思义,无旋treap是不需要旋转的,fhq-treap主要是在分裂(将一棵平衡树分成两棵平衡树)和合并(将两颗平衡树合并为一棵平衡树)这两个操作上进行。
分裂(split)
这里主要介绍按值分裂,即分成的两颗子树中一棵子树的所有值都小于等于val,另一棵子树的所有值都大于val,具体操作请看代码,
split:
inline void split(int now,int val,int &x,int &y)
{
if(!now){//如果这个节点不存在,那么不存在子树
x = y = 0;
return;
}
if(fhq[now].val<=val){//如果该根节点属于x子树
x = now;
split(fhq[now].r, val, fhq[now].r, y);//那么该树的左子树属于x,继续判断右子树
}
else{//如果该根节点属于y子树
y = now;
split(fhq[now].l, val, x, fhq[now].l);////那么该树的右子树属于y,继续判断左子树
}
update(now);
}
合并(merge)
合并是将两棵子树合并为一棵树,是一个递归的过程,合并时,我们保证x子树的所有节点都小于y子树,在合并之后我们需要保证得到的树是一个二叉堆,即每个根节点的优先级大于等于它的儿子结点。
merge:
inline int merge(int x,int y)
{
if(!x||!y)
return x + y;
else{
if(fhq[x].key>fhq[y].key){
fhq[x].r = merge(fhq[x].r, y);
update(x);
return x;
}
else{
fhq[y].l = merge(x, fhq[y].l);
update(y);
return y;
}
}
}
然后我们就可以在这两个操作的基础上做treap的基本操作啦。
插入
如果我们要插入一个值为val的结点,我们可以把现有的树按值(val)分裂成两棵子树,权值小的子树的根节点为x,权值大的子树的根节点为y,然后我们要得到加入值为val的结点只需要将新建的点和两棵子树合并即可。
insert:
inline void insert(int val)
{
z = newnode(val);
split(root, val, x, y);
root = merge(merge(x, z), y);
}
删除
如果我们想要删除一个值为val的点,我们可以把现有的树分为三棵子树,一棵是以x为根节点的所有的权值都小于val的子树,一棵是以y为结点的所有的权值都等于val的子树,最后一棵是以z为结点的所有的权值都大于val的子树,那么这个时候我们要删除一个值为val的点,我们只需要删除掉子树y的根节点即可,即将y的左子树和y的右子树合并起来。
del:
inline void del(int val)
{
split(root, val, x, z);
split(x, val - 1, x, y);
y = merge(fhq[y].l, fhq[y].r);
root = merge(merge(x, y), z);
}
求val的排名
由题意可知,值val的排名=所有权值小于val的结点数+1.所以我们将这棵树按值val-1分裂,值val的排名就等于子树x的结点数+1。
getrank:
inline void getrank(int val)
{
split(root, val - 1, x, y);
printf("%d\n", fhq[x].size + 1);
root = merge(x, y);
}
求排名为x的值val
我们可以通过二分法来一点一点遍历得到答案,如果根节点为root的树的左子树的结点个数大于等于x,那么答案就在左子树中,我们去遍历左子树,如果根节点的左子树的结点个数等于x-1,那么答案就是根节点,如果根节点的左子树的结点个数小于x-1,那么答案在右子树中,我们寻找在右子树中排名第x-fhq[fhq[now].l].size-1的结点的值。
getnum:
inline void getnum(int ran)
{
int now = root;
while(now){
if(fhq[fhq[now].l].size+1==ran){
break;
}
else if(fhq[fhq[now].l].size>=ran){
now = fhq[now].l;
}
else{
ran -= fhq[fhq[now].l].size + 1;
now = fhq[now].r;
}
}
printf("%d\n", fhq[now].val);
}
寻找val的前驱
前驱就是小于val的最大值,我们将现有的子树按值(val-1)分裂为两棵子树,我们在权值较小的子树中一直遍历右子树即可。
pre:
inline void pre(int val)
{
split(root, val - 1, x, y);
int now = x;
while(fhq[now].r){
now = fhq[now].r;
}
printf("%d\n", fhq[now].val);
root = merge(x, y);
}
寻找val的后继
后继定义为大于val的最小值,如果我们将子树按值(val)分裂为两棵子树,我们在权值较大的子树中找到最左边的结点就是答案了。
nex:
inline void nex(int val)
{
split(root, val, x, y);
int now = y;
while(fhq[now].l){
now = fhq[now].l;
}
printf("%d\n", fhq[now].val);
root = merge(x, y);
}
代码:
#include <bits/stdc++.h>
using namespace std;
std::mt19937 rnd(233);
const int maxn=1e5+7;
struct treap
{
int l, r, val, key, size;
} fhq[maxn];
int cnt, root;
inline int newnode(int val)
{
fhq[++cnt].val = val;
fhq[cnt].size = 1;
fhq[cnt].key=rnd();
return cnt;
}
void update(int x)
{
fhq[x].size = fhq[fhq[x].l].size + fhq[fhq[x].r].size+1;
}
inline void split(int now,int val,int &x,int &y)
{
if(!now){
x = y = 0;
return;
}
if(fhq[now].val<=val){
x = now;
split(fhq[now].r, val, fhq[now].r, y);
}
else{
y = now;
split(fhq[now].l, val, x, fhq[now].l);
}
update(now);
}
inline int merge(int x,int y)
{
if(!x||!y)
return x + y;
else{
if(fhq[x].key>fhq[y].key){
fhq[x].r = merge(fhq[x].r, y);
update(x);
return x;
}
else{
fhq[y].l = merge(x, fhq[y].l);
update(y);
return y;
}
}
}
int x, y, z;
inline void insert(int val)
{
z = newnode(val);
split(root, val, x, y);
root = merge(merge(x, z), y);
}
inline void del(int val)
{
split(root, val, x, z);
split(x, val - 1, x, y);
y = merge(fhq[y].l, fhq[y].r);
root = merge(merge(x, y), z);
}
inline void getrank(int val)
{
split(root, val - 1, x, y);
printf("%d\n", fhq[x].size + 1);
root = merge(x, y);
}
inline void getnum(int ran)
{
int now = root;
while(now){
if(fhq[fhq[now].l].size+1==ran){
break;
}
else if(fhq[fhq[now].l].size>=ran){
now = fhq[now].l;
}
else{
ran -= fhq[fhq[now].l].size + 1;
now = fhq[now].r;
}
}
printf("%d\n", fhq[now].val);
}
inline void pre(int val)
{
split(root, val - 1, x, y);
int now = x;
while(fhq[now].r){
now = fhq[now].r;
}
printf("%d\n", fhq[now].val);
root = merge(x, y);
}
inline void nex(int val)
{
split(root, val, x, y);
int now = y;
while(fhq[now].l){
now = fhq[now].l;
}
printf("%d\n", fhq[now].val);
root = merge(x, y);
}
int main()
{
#ifndef ONLINE_JUDGE
//freopen("in.in", "r", stdin);
//freopen("out.out", "w", stdout);
#endif
clock_t c1 = clock();
int n;
cin >> n;
int opt, x;
while(n--){
int opt, x;
cin >> opt >> x;
//cout << "n=" << n << endl;
switch(opt){
case 1:
insert(x);
break;
case 2:
del(x);
break;
case 3:
getrank(x);
break;
case 4:
getnum(x);
break;
case 5:
pre(x);
break;
case 6:
nex(x);
break;
}
}
return 0;
}