#include<iostream>
using namespace std;
/*本代码模拟的是小根堆*/
const int N = 5e5+1, INF = 0x3f3f3f3f;
struct node {
int l, r, val, pos, siz, cnt;
//val:结点的值,pos:随机生成的优先级(尽量避免退化成单链),siz:以该点为根节点的子树大小,cnt:与val值相同的节点个数(搜索二叉树不允许有两个值相同的节点)
};
node tree[N];
int cnt, root; //编号、根节点
int n;
void zig(int &x) { //右旋(为左子节点的右节点);实质:在满足二叉搜索树的性质的基础上比较优先级
int y = tree[x].l;
tree[x].l = tree[y].r;//左子节点的右节点变为父节点x的左子节点(不明白画个图)
tree[y].r = x;//左子节点的右节点变为父节点
tree[y].siz = tree[x].siz;//左子节点继承子树节点数量
tree[x].siz = tree[tree[x].l].siz + tree[tree[x].r].siz + tree[x].cnt;//更新
x = y;//替代(为什么形参里传得是地址)
}
void zag(int &x) { //左旋(为右子节点的左子节点)
int y = tree[x].r;//同上
tree[x].r = tree[y].l;
tree[y].l = x;
tree[y].siz = tree[x].siz;
tree[x].siz = tree[tree[x].l].siz + tree[tree[x].r].siz + tree[x].cnt;
x = y;
}
void insert(int &x, int key) {//插入新节点
if (!x) {//当前节点不存在
x = ++cnt;
tree[x].val = key;
tree[x].pos = rand();
tree[x].l = tree[x].r = 0;
tree[x].cnt = tree[x].siz = 1;
return ;
}
++tree[x].siz;//子树节点数量加一
if (key == tree[x].val) {
++tree[x].cnt;//相同值的数量加一
return ;
}
if (key < tree[x].val) {//二叉搜索树的性质(下同)
insert(tree[x].l, key);
if (tree[x].pos > tree[tree[x].l].pos)
zig(x);
return ;
}
if (key > tree[x].val) {
insert(tree[x].r, key);
if (tree[x].pos > tree[tree[x].r].pos)
zag(x);
return ;
}
}
void del(int &x, int key) {//删除值为key的旧节点
if (tree[x].val == key) {//当前结点的值恰好为key,所以接下来就不必进行
if (tree[x].cnt > 1) {//存在多个
--tree[x].cnt;
--tree[x].siz;
return;
}
if (!tree[x].l || !tree[x].r)//没有子节点或类似于一条单链
x = tree[x].l + tree[x].r;//相当于用子节点直接替代或变为叶子节点
else {
if (tree[tree[x].l].pos < tree[tree[x].r].pos){
//因为x将要被删除(所以讨论儿子),所以为了保证满足堆的性质,所以要先使用左旋或右旋,在删除
//模拟的是小根堆(根节点优先级小于子节点)
zig(x);
del(x, key);
}
else{
zag(x);
del(x, key);
}
}
return;
}
--tree[x].siz;
if (key < tree[x].val)//二叉搜索树的性质
del(tree[x].l, key);
else
del(tree[x].r, key);
}
int getpre(int key) {//得到前驱(比key小的最大值)
int x = root, pre = -INF;
//pr:找到的上一个前驱
while (x) {//直至找到叶子节点
if (tree[x].val < key)
//既然当前节点的值比key小,满足前提条件,说明我们要去它的右子树去寻找尽可能大的值(BST二叉搜索树的性质)
pre = tree[x].val, x = tree[x].r;
else
//如果连前提条件都不满足,即当前节点的值不小于key,那么就要去左子树里去找了
x = tree[x].l;
}
return pre;//返回值
}
int getnex(int key) {//得到后继(比key大的最小值)(类似于前驱)
int x = root, nex = INF;
while (x) {
if (tree[x].val > key)
nex = tree[x].val, x = tree[x].l;
else
x = tree[x].r;
}
return nex;
}
int queryval(int k) {//查询排名所对应的值
int x = root;
while (x) {
if (k > tree[tree[x].l].siz && k <= tree[tree[x].l].siz + tree[x].cnt)//排名在子树范围内
return tree[x].val;
if (k <= tree[tree[x].l].siz)//小于范围,找左子树
x = tree[x].l;
else {//大于范围,找右子树
//通常来说,根节点与左子树被看作是一体的,与右子树区分开,
//例如ls=1,2,3 x=5 rs=6,7,那么3在树和子树中都是第三个,而6在树中是第5名,在右子树中是第1名
k -= tree[tree[x].l].siz + tree[x].cnt;
x = tree[x].r;
}
}
return 0;
}
int queryrank(int key) {//查询值所对应的排名
int x = root, res = 0;
while (x) {
if (key == tree[x].val)//刚好相等
return res + tree[tree[x].l].siz + 1;//从左子树的范围转移过来
if (key < tree[x].val)//小于值(根据性质,去找左子树)
x = tree[x].l;
else {//大于值(根据性质,去找右子树)
res += tree[tree[x].l].siz + tree[x].cnt;
x = tree[x].r;
}
}
return res;
}
int main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n;
int op, x;
for (int i = 1; i <= n; ++i) {
cin >> op >> x;
switch (op) {
case 1:
insert(root, x);
break;
case 2:
del(root, x);
break;
case 3:
cout << queryrank(x) << '\n';
break;
case 4:
cout << queryval(x) << '\n';
break;
case 5:
cout << getpre(x) << '\n';
break;
case 6:
cout << getnex(x) << '\n';
break;
}
}
return 0;
}