#include <iostream>
using namespace std;
// 定义最大节点数量,限制树的规模
const int N = 100005;
// 定义Treap树的节点结构
struct node {
int l, r; // 左右儿子的索引(0表示空节点)
int val; // 节点存储的值(用于维护二叉搜索树BST性质:左子树<=根<=右子树)
int rnd; // 随机值(用于维护堆性质:父节点rnd <= 子节点rnd,保证树平衡)
int size; // 以当前节点为根的子树中节点总数(用于快速计算排名等)
} tr[N]; // 用数组存储所有节点,tr[i]表示索引为i的节点
int root; // 根节点的索引(初始为0,代表空树)
int idx; // 节点计数器,用于分配新节点的唯一索引(从1开始递增)
// 创建新节点:为值v分配一个新节点,并用x接收新节点的索引
void newnode(int &x, int v) {
x = ++idx; // 分配新索引(idx自增后赋值给x)
tr[x].val = v; // 设置节点的值为v
tr[x].rnd = rand(); // 随机生成rnd值(用于后续维护堆性质)
tr[x].size = 1; // 新节点初始只有自身,子树大小为1
// 新节点的左右儿子默认为0(空),因全局变量初始值为0
}
// 更新节点p的子树大小(当左右子树变化时必须调用)
void pushup(int p) {
// 子树大小 = 左子树大小 + 右子树大小 + 1(当前节点)
tr[p].size = tr[tr[p].l].size + tr[tr[p].r].size + 1;
}
// 分裂操作:将以p为根的树按值v分裂为两棵树
// x为左树(所有节点值<=v),y为右树(所有节点值>v)
void split(int p, int v, int &x, int &y) {
if (!p) { // 若当前节点为空(p=0)
x = y = 0; // 分裂后的两棵树也为空
return;
}
if (tr[p].val <= v) { // 当前节点值<=v,应属于左树x
x = p; // 左树x的根设为当前节点p
// 递归分裂p的右子树:右子树中<=v的部分成为p的新右子树(仍属x),>v的部分成为y的一部分
split(tr[x].r, v, tr[x].r, y);
pushup(x); // 因右子树变化,更新x的子树大小
} else { // 当前节点值>v,应属于右树y
y = p; // 右树y的根设为当前节点p
// 递归分裂p的左子树:左子树中<=v的部分成为x的一部分,>v的部分成为p的新左子树(仍属y)
split(tr[y].l, v, x, tr[y].l);
pushup(y); // 因左子树变化,更新y的子树大小
}
}
// 合并操作:将两棵树x和y合并为一棵(前提:x中所有节点值<=y中所有节点值)
// 返回合并后的根节点索引
int merge(int x, int y) {
if (!x || !y) return x + y; // 若有一棵树为空,直接返回另一棵(0+x=x,x+0=x)
// 比较rnd值,维持堆性质(rnd小的节点作为父节点)
if (tr[x].rnd < tr[y].rnd) {
// x的rnd更小,x作为根,将x的右子树与y合并后作为x的新右子树
tr[x].r = merge(tr[x].r, y);
pushup(x); // 因右子树变化,更新x的子树大小
return x; // 返回新根x
} else {
// y的rnd更小,y作为根,将x与y的左子树合并后作为y的新左子树
tr[y].l = merge(x, tr[y].l);
pushup(y); // 因左子树变化,更新y的子树大小
return y; // 返回新根y
}
}
// 插入操作:向树中插入值v
void insert(int v) {
int x, y, z; // 临时变量用于分裂和合并
split(root, v, x, y); // 分裂根节点:x含<=v的节点,y含>v的节点
newnode(z, v); // 创建值为v的新节点z
// 先合并x和z(保证x中节点<=z),再合并结果与y(保证合并后节点<=y),得到新根
root = merge(merge(x, z), y);
}
// 删除操作:从树中删除值v
void del(int v) {
int x, y, z; // 临时变量
split(root, v, x, z); // 第一次分裂:x含<=v,z含>v
split(x, v-1, x, y); // 第二次分裂:x含<=v-1,y含=v的节点(精确分离要删除的节点)
// 合并y的左右子树(相当于删除y中所有值为v的节点)
y = merge(tr[y].l, tr[y].r);
// 重新合并所有部分,恢复树结构
root = merge(merge(x, y), z);
}
// 查询值v的排名(即有多少个数小于v,结果+1)
int getrank(int v) {
int x, y;
split(root, v-1, x, y); // 分裂出所有<=v-1的节点到x(这些节点都小于v)
int ans = tr[x].size + 1; // x的大小是小于v的数量,+1即为v的排名
root = merge(x, y); // 合并回原树(不改变树结构)
return ans;
}
// 查询排名为v的值(第v小的数)
int getval(int root, int v) {
// 左子树大小+1等于v,说明当前节点就是第v小
if (v == tr[tr[root].l].size + 1)
return tr[root].val;
// 若v小于左子树大小,说明第v小在左子树中
else if (v <= tr[tr[root].l].size)
return getval(tr[root].l, v);
// 否则在右子树中,v需减去左子树大小和当前节点(1)
else
return getval(tr[root].r, v - tr[tr[root].l].size - 1);
}
// 查询v的前驱(小于v的最大数)
int getpre(int v) {
int x, y, s, ans;
split(root, v-1, x, y); // 分裂出所有<=v-1的节点到x(这些节点都小于v)
s = tr[x].size; // x的大小是<=v-1的节点数量
ans = getval(x, s); // 取x中最大的数(第s小,即最后一个元素)
root = merge(x, y); // 合并回原树
return ans;
}
// 查询v的后继(大于v的最小数)
int getnxt(int v) {
int x, y, ans;
split(root, v, x, y); // 分裂出所有<=v的节点到x,y中节点都>v
ans = getval(y, 1); // 取y中最小的数(第1小,即第一个元素)
root = merge(x, y); // 合并回原树
return ans;
}
int main() {
int n, op, v; // n:操作总数;op:操作类型(1-6);v:操作参数
scanf("%d", &n); // 读取操作数量
for (int i = 1; i <= n; ++i) { // 循环处理每个操作
scanf("%d%d", &op, &v); // 读取操作类型和参数
if (op == 1) insert(v); // 1:插入值v
else if (op == 2) del(v); // 2:删除值v
else if (op == 3) printf("%d\n", getrank(v)); // 3:查询v的排名
else if (op == 4) printf("%d\n", getval(root, v)); // 4:查询第v小的值
else if (op == 5) printf("%d\n", getpre(v)); // 5:查询v的前驱
else printf("%d\n", getnxt(v)); // 6:查询v的后继
}
return 0;
}