平衡树
二叉搜索树
二叉搜索树,又称BST。
该点所有左子树节点小于该点,右子树节点大于它。
中序遍历是有序的。
插入操作:
向下深搜,若当前节点为空则新建节点,否则讨论当前节点的值是否大于我要插入的值,大于则向左子树走,否则向右子树走。
查询k大值:
向下深搜,若左子树size>=k则搜左子树,若k=左子树size+1则返回当前节点,否则k减掉左子树size+1并搜索右子树
查询排名同理。
Splay
通过不断将某个节点旋转到根节点,使得整棵树仍然满足BST的性质,而且保持平衡不至于退化成链。
struct node{
int s[2];//左右儿子 0左1右
int fa;//父亲
int v;//节点权值
int cnt;//权值出现次数
int size;//子树大小
void init(int fa1,int v1)
fa=fa1;v=v1;
cnr=size=1;
}
}t[N];
int root;
int tot;
1. 旋转
要求:保序且信息正确。


void rotate(int x){//旋转
int y=t[x].fa,z=t[y].fa;//y是x的父亲,z是x的爷爷
int k=t[y].s[1]==x;//k代表x是y的左儿子还是右儿子
t[y].s[k]=t[x].s[k^1];//把x的左/右儿子给y
t[t[x].s[k^1]].fa=y;
t[x].s[k^1]=y;//把y旋转为x的儿子
t[y].fa=x;
t[z].s[t[z].s[1]==y]=x;
t[x].fa=z;
pushup(x);pushup(x);
}
void pushup(int k){
t[k].size=t[t[k].s[0]].size+t[t[k].s[1]].size+t[k].cnt;
2. 伸展splay
访问节点x,并把x旋转到根节点
- 父节点为根。
单旋,直接进行旋转。 - 直线型,父节点不是根。
做双旋。
先将父节点和爷爷节点旋转,再父节点和x旋转。 - 折线型
做双旋。
先将x与父节点旋转,再将x与爷爷旋转。
void splay(int x,int k){//x要旋转的节点,k目标
while(t[x].fa!=k){
int y=t[x].fa,z=t[y].fa;
if(z!=k)//需要做双旋 直转中 折转底
(t[y].s[0]==x)^(t[z].s[0]==y)?rotate(x):rotate(y);//判断转哪个点
rotate(x);
}
if(k==0) root=x;//直接转到根
}
3. 查找
找到v所在节点,并把该节点转到根
void find(int v){//找到v并转到根
int x=root;//设为根,从根开始找
while(t[x].s[v>t[x].v]]&&v!=t[x].v)
x=t[x].s[v>t[x].v];//如果v大于当前值则往左子树找,否则向右找。若找到了或没有节点了就结束。
slpay(x,0);
}
4.前驱后继
int get_pre(int v){
find(v);//先找到这个点并转到根节点上来
int x=root;//从根节点开始找
if(t[x].v<v)return x;//若树中没有出现过v,则返回最接近v的。
x=t[x].s[0];
while(t[x].s[1])x=t[x].s[1];
return x;
}
int get_suc(int v){
find(v);//先找到这个点并转到根节点上来
int x=root;//从根节点开始找
if(t[x].v>v)return x;//若树中没有出现过v,则返回最接近v的。
x=t[x].s[1];
while(t[x].s[0])x=t[x].s[0];
return x;
}
5. 删除
把一个要删除的点的前驱转为根节点,后继转为前驱的子树,则要删除的点必然在后继节点的叶结点上,便于删除。
void del(int v){//删除
int pre=get_pre(v);
int suc=get_soc(v);
splay(pre,0);splay(suc,pre);
int del=t[suc].s[0];
if(t[del].cnt>1)
t[del].cnt--,splay(del,0);//删除之后做更新
else
t[suc].s[0]=0,splay(suc,0);
}
6. 查k的排名
int get_rank(int v){//排名
find(v);//转到根上去
return t[t[root].s[0]].size;//直接返回左子树的大小就行了
}
7. 查排名为k的数值
int get_val(int k){//数值
int x=root;
while(1){
int y=t[x].s[0];
if(t[y].size+t[x].cnt<k){
k-=t[y].size+t[x].cnt;
x=t[x].s[1];
}
else {
if(t[y].size>=k)x=t[x].s[0];
else break;
}
splay(x,0);
return t[x].v;
}
8. 插入
void Insert(int v){
int x=root,fa=0;
while(x&&t[x].v!=v)//要么为空节点,要么找到了
fa=x,x=t[x].s[v>t[x].v];
if(x) t[x].cnt++;
else {
++tot;
t[fa].s[v>t[fa].v]=x;
t[x].init(fa,v);
}
splay(x,0);
}
维护区间翻转
手摸一下便知,假设总区间为 \([L,R]\) ,要翻转的区间为 \([l,r]\) ,我们将 \(l-1\) 翻转到根节点后,将 \(r+1\) 翻转到 \(l-1\) 的右子树,此时 \([l,r]\) 正好翻转一次。
但是通常由于每次翻转都执行的话时间复杂度很高,所以就打 \(tag\) (即懒标) 来优化。
trick:要加一个正无穷大和一个负无穷大,方便删除最大和最小值。
const int N=1e6+10;
int n,x,op,tot,root;
struct node{
int s[2];//左右儿子 0左1右
int fa;//父亲
int v;//节点权值
int cnt;//权值出现次数
int size;//子树大小
void init(int fa1,int v1){
fa=fa1;v=v1;
cnt=size=1;
}
}t[N];
void pushup(int k){t[k].size=t[t[k].s[0]].size+t[t[k].s[1]].size+t[k].cnt;}
void rotate(int x){//旋转
int y=t[x].fa,z=t[y].fa;//y是x的父亲,z是x的爷爷
int k=t[y].s[1]==x;//k代表x是y的左儿子还是右儿子
t[y].s[k]=t[x].s[k^1];//把x的左/右儿子给y
t[t[x].s[k^1]].fa=y;
t[x].s[k^1]=y;//把y旋转为x的儿子
t[y].fa=x;
t[z].s[t[z].s[1]==y]=x;
t[x].fa=z;
pushup(x);pushup(y);
}
void splay(int x,int k){//x要旋转的节点,k目标
while(t[x].fa!=k){
int y=t[x].fa,z=t[y].fa;
if(z!=k)//需要做双旋 直转中 折转底
(t[y].s[0]==x)^(t[z].s[0]==y)?rotate(x):rotate(y);//判断转哪个点
rotate(x);
}
if(k==0) root=x;//直接转到根
}
void find(int v){//找到v并转到根
int x=root;//设为根,从根开始找
while(t[x].s[v>t[x].v]&&v!=t[x].v)
x=t[x].s[v>t[x].v];//如果v大于当前值则往左子树找,否则向右找。若找到了或没有节点了就结束。
splay(x,0);
}
void Insert(int v){
int x=root,fa=0;
while(x&&t[x].v!=v)//要么为空节点,要么找到了
fa=x,x=t[x].s[v>t[x].v];
if(x) t[x].cnt++;
else {
x=++tot;
t[fa].s[v>t[fa].v]=x;
t[x].init(fa,v);
}
splay(x,0);
}
int get_rank(int v){//排名
find(v);//转到根上去
return t[t[root].s[0]].size;//直接返回左子树的大小就行了
}
int get_pre(int v){
find(v);//先找到这个点并转到根节点上来
int x=root;//从根节点开始找
if(t[x].v<v)return x;//若树中没有出现过v,则返回最接近v的。
x=t[x].s[0];
while(t[x].s[1])x=t[x].s[1];
return x;
}
int get_suc(int v){
find(v);//先找到这个点并转到根节点上来
int x=root;//从根节点开始找
if(t[x].v>v)return x;//若树中没有出现过v,则返回最接近v的。
x=t[x].s[1];
while(t[x].s[0])x=t[x].s[0];
return x;
}
void del(int v){//删除
int pre=get_pre(v);
int suc=get_suc(v);
splay(pre,0);splay(suc,pre);
int del=t[suc].s[0];
if(t[del].cnt>1)
t[del].cnt--,splay(del,0);//删除之后做更新
else
t[suc].s[0]=0,splay(suc,0);
}
int get_val(int k){//数值
int x=root;
while(1){
int y=t[x].s[0];
if(t[y].size+t[x].cnt<k){
k-=t[y].size+t[x].cnt;
x=t[x].s[1];
}
else {
if(t[y].size>=k)x=t[x].s[0];
else break;
}
}
splay(x,0);
return t[x].v;
}
signed main(){
Insert(-inf);Insert(inf);
n=read();
while(n--){
op=read();x=read();
if(op==1){//add
Insert(x);
}
else if(op==2){//delate
del(x);
}
else if(op==3){//rank
cout<<get_rank(x)<<'\n';
}
else if(op==4){// k rank'number
cout<<get_val(x+1)<<'\n';
}
else if(op==5){//pre
cout<<t[get_pre(x)].v<<'\n';
}
else if(op==6){//suc
cout<<t[get_suc(x)].v<<'\n';
}
}
return 0;
}
Treap
利用堆的性质来维护平衡的一种平衡树。
每个节点在建立时,赋予其一个随机值,通过旋转操作维护使得父节点的随机值永远小于等于子节点的随机值,这样可以使得树高期望保证在O(logn)。
插入:
像一般二叉树那样插入,在回溯时判断,若插入的那棵子树的根节点的随机值小于当前节点,则将该节点旋转至当前节点的位置
删除:
向下深搜找到要删除的节点,若当前节点没有左(右)子树则用右(左)子树替代当前节点,否则左旋并深搜左子树直到某一子树为空为止。回溯的时候同插入。
注意二叉搜索树不允许存在两个权值相同的节点,若遇到插入两个相同值的时候要把这两个值记录在同一个节点上。
满足BST和heap的性质。
包含关键字和优先级两个部分。
关键字满足BST,优先级满足堆。
树堆性质:
如果插入顺序随机,则二叉搜索树的深度是\(O(\log n)\)
随机的优先级即随机插入顺序。
查找从根节点开始。
删除,把要删除的节点转到叶节点。
treap是利用旋转维护性质,FHQ是利用合并和分裂维护。
无旋treap叫FHQ。
//Treap
const int N=1e6+10;
int n,op,x,tot,root;
namespace Treap{
struct node{
int s[2];//左右儿子
int val;//原本的值
int key;//优先级
int size;
int cnt;
}t[N];
void pushup(int k){t[k].size=t[t[k].s[0]].size+t[t[k].s[1]].size+t[k].cnt;}
int Newnode(int v){
t[++tot].val=v;
t[tot].key=rand();
t[tot].cnt=t[tot].size=1;
return tot;
}
void rotate(int &k,int d){//k是引用传递,d表示方向
int y=t[k].s[d^1],z=t[y].s[d];
t[k].s[d^1]=z;
t[y].s[d]=k;
k=y;
pushup(t[k].s[d]);
pushup(k);
}
void build(){//初始化
root=Newnode(-inf);t[root].s[1]=Newnode(inf);
pushup(root);
}
//
void Insert(int &k,int v){
if(!k){
k=Newnode(v);
return ;
}
if(v==t[k].val) t[k].cnt++;
else {
int K=v<t[k].val?0:1;
Insert(t[k].s[K],v);
if(t[k].key<t[t[k].s[K]].key) rotate(k,K^1);//与左节点交换右旋,与右节点交换左旋
}
pushup(k);
}
//
void Remove(int &k,int v){
if(!k) return ;//找不到这个值
if(v==t[k].val) {//找到了
if(t[k].cnt>1){//出现了多次
t[k].cnt--;
pushup(k);
return ;
}
if(t[k].s[1]||t[k].s[0]){//只有一个值,把值旋转到叶节点,删去
if(!t[k].s[1]||t[t[k].s[0]].key>t[t[k].s[1]].key){//删去当前点后,优先级小的补上来
rotate(k,1),Remove(t[k].s[1],v);//右旋是与左儿子交换,当前点变成右节点;左旋则是与右儿子交换,当前点变为左节点
}
else rotate(k,0),Remove(t[k].s[0],v);
pushup(k);
}
else k=0;
return ;
}
v < t[k].val ? Remove(t[k].s[0],v) : Remove(t[k].s[1],v);//BST性质
pushup(k);
}
//
int Get_rk(int k,int v){
if(!k)return 0;
if(t[k].val==v) return t[t[k].s[0]].size+1;
else if(v<t[k].val) return Get_rk(t[k].s[0],v);
else return t[t[k].s[0]].size+t[k].cnt+Get_rk(t[k].s[1],v);
}
//
int Get_val(int k,int x){//
if(!k) return inf;
if(x<=t[t[k].s[0]].size) return Get_val(t[k].s[0],x);//Wrong
else if(t[t[k].s[0]].size+t[k].cnt>=x) return t[k].val;
else return Get_val(t[k].s[1],x-t[t[k].s[0]].size-t[k].cnt);
}
//
int Get_pre(int v){
int x=root,pre;
while(x){
if(t[x].val<v) pre=t[x].val,x=t[x].s[1];
else x=t[x].s[0];
}
return pre;
}
//
int Get_suc(int v){
int x=root,suc;
while(x){
if(t[x].val>v) suc=t[x].val,x=t[x].s[0];
else x=t[x].s[1];
}
return suc;
}
}
signed main(){
Treap::build();
n=read();
while(n--){
op=read();x=read();
if(op==1){
Treap::Insert(root,x);
}
else if(op==2){
Treap::Remove(root,x);
}
else if(op==3){
cout<<Treap::Get_rk(root,x)-1<<'\n';//因为插入了-inf,inf
}
else if(op==4){
cout<<Treap::Get_val(root,x+1)<<'\n';//因为插入了-inf
}
else if(op==5){
cout<<Treap::Get_pre(x)<<'\n';
}
else if(op==6){
cout<<Treap::Get_suc(x)<<'\n';
}
}
return 0;
}
FHQ Treap
struct node{
int l,r;
int val;
int key;
int size;
}t[N];
int x,root,tot;
构造新节点
int Newnode(int v){
t[++tot].val=v;
t[tot].key=rand();
t[tot].size=1;
return tot;
向上更新
void pushup(int k){
t[k].size=t[t[k].l].size+t[t[k].r].size+1;
1. 分裂
按值分裂:根据给的值v把一棵树分裂成两棵树,一棵树的值\(val\leq v\),另一棵树的\(val>v\)。
void split(int k,int v,int &x,int &y){//k当前节点,v是给定的值,引用xy最终分别指向左右treap的根
if(!k){x=y=0;return ;}
if(t[k].val<=v){
x=k;
split(t[x].r,v,t[x].r.y);}
else {
y=k;
slipt(t[x].l,v,x,t[x].l);
}
pushup(k);
}
\(O(\log n)\)
2. 合并
左treap的根指针x、右treap的根指针y,必须满足x中所有节点的val值小于等于y中所有节点的val值。
因为两个treap已经有序,所以在合并的时候只需要考虑哪个书放在上面,也就是放个作为子树。根据堆的性质,把优先级小的放在上面。
int merge(int x,int y){//要合并的编号
if(!x||!y)return x+y;
if(t[x].key<t[y].key){
t[x].r=merge(t[x].r,y);
pushup(x);return x;}
else {
t[y].l=merge(x,t[y].l);
pushup(y);return y;}
}
3.插入
先分裂,再加入新点,再合并。
void insert(int v){
int x,y,z;
split(root,v,x,y);
z=newnode(y);
root=merge(merge(x,z),y);
}
4. 删除
void del(int v){
int x,y,z;
split(root,v,x,z);//分裂成两棵树,一颗val小于等于v(x),一颗大于v(z)
split(x,v-1,x,y);//再将val小于等于v的树分裂,分裂成等于v和小于v的两棵树
y=merge(t[y].l,t[y].r);//将等于y的那棵树的左右儿子合并,此时正好将根节点删除
root=merge(merge(x,y),z);最后再合并
}
5. 找第k个点
int get_k(int p,int k){//p是当前节点,k是要找的节点
if(k<=t[t[k].l].size)
return get_k(t[k].l,k);
if(k==t[t[k].l].size+1)
return k;
return get_k(t[k].r,k-t[t[k].l].size-1);
}
6. 前驱后继
void get_pre(int v){
int x,y;
split(root,v-1,x,y);//分裂出小于v和大于等于v的两棵树
int p=ger_k(x,t[x].size);
cout<<t[p].val<<'\n';
root=merge(x,y);
}
void get_suc(int v){
int x,y;
split(root,v,x,y);//分裂出小于等于v和大于v的两棵树
int p=get_k(y,1);
cout<<t[p].val<<'\n';
root=merge(x,y);
}
7. 查该数的排名
void get_rank(int v){
int x,y;
split(root,v-1,x,y);
cout<<t[x].size+1<<'\n';
root=merge(x,y);
}
8. 查排名为k的数值
void get_val(int k){
int p=get_k(root,k);//找第k个点
cout<<t[p].val<<'\n';
}
const int N=2e6+10;
int n,m,l,r,root,tot;
namespace FHQ{
struct node{
int l,r;
int val;
int key;
int size;
int tag;
}t[N];
int Newnode(int v){
t[++tot].val=v;
t[tot].key=rand();
t[tot].size=1;
return tot;
}
void pushup(int k){
t[k].size=t[t[k].l].size+t[t[k].r].size+1;
}
void pushdown(int x){//下传标记
if(!t[x].tag) return ;
swap(t[x].l,t[x].r);
t[t[x].l].tag^=1;
t[t[x].r].tag^=1;
t[x].tag=0;
}
void split(int k,int v,int &x,int &y){
if(!k){
x=y=0;
return ;
}
pushdown(k);//先下传
if(t[t[k].l].size<v){//往右子树找
v-=t[t[k].l].size+1;//减去左子树的size和当前大小,感性理解
x=k;
split(t[k].r,v,t[k].r,y);
}
else {
y=k;
split(t[k].l,v,x,t[k].l);
}
pushup(k);
}
int merge(int x,int y){//要合并的编号
if(!x||!y)return x+y;
if(t[x].key<t[y].key){//维护小根堆
pushdown(x);
t[x].r=merge(t[x].r,y);
pushup(x);
return x;
}
else {
pushdown(y);
t[y].l=merge(x,t[y].l);
pushup(y);
return y;
}
}
void output(int x){
pushdown(x);
if(t[x].l) output(t[x].l);
if(t[x].val>=1&&t[x].val<=n) cout<<t[x].val<<" ";//过滤哨兵
if(t[x].r) output(t[x].r);
}
}
signed main(){
n=read();m=read();
rep(i,1,n){
root=FHQ::merge(root,FHQ::Newnode(i));
}
rep(i,1,m){
l=read();r=read();
int x,y,z;
FHQ::split(root,r,x,z);
FHQ::split(x,l-1,x,y);
FHQ::t[y].tag^=1;
root=FHQ::merge(FHQ::merge(x,y),z);
}
FHQ::output(root);
return 0;
}
可持久化平衡树
可持久化的结构:无旋Treap。即FHQ。
用动态开点维护历史版本。
分裂必须动态开点,别的可不用。
const int N=5e5+10;
int root[N],tot;
struct node{
int l,r;
int val;
int key;//随机值
int size;
}t[N*50];
构造新节点:
void Newnode(int &x,int v){//与普通FHQ不同,要动态开点
x=++tot;
t[x].val=v;
t[x].key=rand();
t[x].size=1;
}
void pushup(int k){
t[k].size=t[t[k].l].size+t[t[k].r].size+1;
}
分裂
未可持久化的:
void split(int k,int v,int &x,int &y){//按值
if(!k){
x=y=0;
return ;
}
if(t[k].val<=v){//往右子树找
x=k;
split(t[k].r,v,t[k].r,y);
pushup(x);
}
else {
y=k;
split(t[k].l,v,x,t[k].l);
pushup(y);
}
}
可持久化的:
void split(int k,int v,int &x,int &y){//按值分裂
if(!p){
x=y=0;
return ;
}
if(t[k].val<=v){
x=++tot;//动态开点
t[x]=t[k];
split(t[x].r,v,t[x].r,y);
pushup(x);
}
else {
y=++tot;
t[y]=t[k];
split(t[y].l,v,x,t[y].l);
pushup(y);
}
}
合并
未可持久化的:
int merge(int x,int y){//要合并的编号
if(!x||!y)return x+y;
if(t[x].key<t[y].key){//维护小根堆
t[x].r=merge(t[x].r,y);
pushup(x);
return x;
}
else {
t[y].l=merge(x,t[y].l);
pushup(y);
return y;
}
}
可持久化的:
int merge(int x,int y){
if(!x||!y)
return x+y;
int k=++tot;
if(t[x].key<t[y].key){
t[k]=t[x];
t[k].r=merge(t[k].r,y);
pushup(k); return k;
}
else {
t[k]=t[y];
t[k].l=merge(x,t[k].l);
pushup(k); return k;
}
}
合并不一定要动态开点,因为前面已经复制过了。
插入
void Insert(int &k,int v){
int x,y,z;
split(k,v,x,y);//先分裂,边分裂边复制
Newnode(z,v);//新创点
k=merge((x,z),y);//可不用动态开点
}
删除
void del(int &k,int v){
int x,y,z;
split(k,v,x,z);//x->[1,v] z->(v,n]
split(x,v-1,x,y);//x->[1,v-1],y->[v]
y=merge(t[y].l,t[y].r);//将左右子树合并,即删去根节点
k=merge(merge(x,y),z);
}
查询操作
查询操作也可以不用动态开点,因为没有进行修改。
int Get_rk(int &k,int v){//查排名
int x,y;
split(root,v-1,x,y);
int ans=t[x].size+1;
k=merge(x,y);
return ans;
}
int Get_val(int k,int v){//排名为k的数值
if(t[t[k].l].size>=v){//左子树找
return Get_val(t[k].l,v);
}
else if(t[t[k].l].size+1==v) return t[k].val;
return Get_val(t[k].r,v-t[t[k].l].size-1);
}
int Get_pre(int &k,int v){//找前驱
int x,y,s,ans;
split(k,v-1,x,y);
if(!x) return -inf;
s=t[x].size;
ans=Get_val(x,s);//在[1,v-1]中找最大的即可
k=merge(x,y);
return ans;
}
int Get_suc(int &k,int v){//找后继
int x,y,ans;
split(k,v,x,y);
if(!y) return inf;
ans=Get_val(y,1);
k=merge(x,y);
return ans;
}
const int N=5e5+10;
int n,v,op,x,root[N],tot;
namespace FHQ{
struct node{
int l,r;
int val;
int key;//随机值
int size;
}t[N*50];
void Newnode(int &x,int v){//与普通FHQ不同,要动态开点
x=++tot;
t[x].val=v;
t[x].key=rand();
t[x].size=1;
}
void pushup(int k){
t[k].size=t[t[k].l].size+t[t[k].r].size+1;
}
void split(int k,int v,int &x,int &y){//按值分裂
if(!k){
x=y=0;
return ;
}
if(t[k].val<=v){
x=++tot;//动态开点,所经过的点都重新开一个
t[x]=t[k];
split(t[x].r,v,t[x].r,y);
pushup(x);
}
else {
y=++tot;
t[y]=t[k];
split(t[y].l,v,x,t[y].l);
pushup(y);
}
}
int merge(int x,int y){//要合并的编号
if(!x||!y)return x+y;
if(t[x].key<t[y].key){//维护小根堆
t[x].r=merge(t[x].r,y);
pushup(x);
return x;
}
else {
t[y].l=merge(x,t[y].l);
pushup(y);
return y;
}
}
void Insert(int &k,int v){
int x,y,z;
split(k,v,x,y);//先分裂,边分裂边复制
Newnode(z,v);//新创点
k=merge(merge(x,z),y);//可不用动态开点 //Wrong
}
void del(int &k,int v){
int x,y,z;
split(k,v,x,z);//x->[1,v] z->(v,n]
split(x,v-1,x,y);//x->[1,v-1],y->[v]
y=merge(t[y].l,t[y].r);//将左右子树合并,即删去根节点
k=merge(merge(x,y),z);
}
int Get_rk(int &k,int v){
int x,y;
split(k,v-1,x,y);
int ans=t[x].size+1;
k=merge(x,y);
return ans;
}
int Get_val(int k,int v){//排名为k的数值
if(t[t[k].l].size+1==v) return t[k].val;
else if(t[t[k].l].size>=v){//左子树找
return Get_val(t[k].l,v);
}
return Get_val(t[k].r,v-t[t[k].l].size-1);
}
int Get_pre(int &k,int v){
int x,y,s,ans;
split(k,v-1,x,y);
if(!x) return -inf;
s=t[x].size;
ans=Get_val(x,s);//在[1,v-1]中找最大的即可
k=merge(x,y);
return ans;
}
int Get_suc(int &k,int v){
int x,y,ans;
split(k,v,x,y);
if(!y) return inf;
ans=Get_val(y,1);
k=merge(x,y);
return ans;
}
}
signed main(){
n=read();
rep(i,1,n){
v=read();op=read();x=read();
root[i]=root[v];
if(op==1){
FHQ::Insert(root[i],x);
}
else if(op==2){
FHQ::del(root[i],x);
}
else if(op==3){
cout<<FHQ::Get_rk(root[i],x)<<'\n';
}
else if(op==4){
cout<<FHQ::Get_val(root[i],x)<<'\n';
}
else if(op==5){
cout<<FHQ::Get_pre(root[i],x)<<'\n';
}
else if(op==6){
cout<<FHQ::Get_suc(root[i],x)<<'\n';
}
}
return 0;
}

浙公网安备 33010602011771号