https://www.luogu.org/problemnew/show/P3369
写一棵平衡树维护一些数据
包括插入,查找,删除,查找前驱,查找后继等
平衡树初见。。。
平衡树有很多版本,它们的祖宗都是一种叫二叉查找树的东西,简称 BST B S T ,那为什么又会有平衡树这种东西呢?因为在最糟糕的情况下, BST B S T 的时间复杂度将会掉落到 O(n) O ( n ) ,这就引入了平衡树的概念。
平衡树就是一种 xjb x j b 乱搞使得二叉查找树重新平衡的东西
听 BPM B P M 大佬说,每棵平衡树保持平衡的方法都不一样,它们各有各自的优势,比较优秀的有这么几个
Splay(扩展性好),红黑树,AVL树,Treap(树堆),SBT,替罪羊树(“暴力”) S p l a y ( 扩 展 性 好 ) , 红 黑 树 , A V L 树 , T r e a p ( 树 堆 ) , S B T , 替 罪 羊 树 ( “ 暴 力 ” )
似乎好像最骚的就是替罪羊树了,不过我们先不讨论这个玩意儿,我们来讲( che c h e )一下 Treap T r e a p (树堆)
首先,顾名思义,树堆就是一棵满足堆性质的二叉查找树,
注意,其并不等于二叉堆,二叉堆必须是一棵完全二叉树,但树堆并不一定是
Look at it!↓ L o o k a t i t ! ↓
新建一个点,随机一个值给该点,复杂度 O(1) O ( 1 )
ILI New(RI val)
{
t[++tot].val=val;
t[tot].dat=rand();
t[tot].cnt=t[tot].size=1;
return tot;
}
由于该题还需查询前驱啥的,所以我们还需要打一个东西去维护其的序号,复杂度 O(1) O ( 1 )
ILV Updata(RI k)
{
t[k].size=t[t[k].l].size+t[t[k].r].size+t[k].cnt;
return;
}
即新建建立根节点和区分正数和负数的东西,假定其为无穷大,时间复杂度 O(1) O ( 1 )
ILV Build()
{
New(-INF),New(INF);
root=1;t[1].r=2;
Updata(root);
}
给节点随机分配一个优先级,先和二叉搜索树的插入一样,先把要插入的点插入到一个叶子上,然后跟维护堆一样,如果当前节点的优先级比根大就旋转,如果当前节点是根的左儿子就右旋如果当前节点是根的右儿子就左旋。
由于旋转是 O(1) O ( 1 ) 的,最多进行 h h 次( h h 是树的高度),插入的复杂度是 O(h) O ( h ) 的,在期望情况下 h=O(logn) h = O ( l o g n ) ,所以它的期望复杂度是 O(logn) O ( l o g n ) 。
ILV Insert(RI &k,RI val)//插入
{
if(!k)//不存在的
{
k=New(val);//新建
return;
}
if(val==t[k].val)
{
t[k].cnt++;
Updata(k);
return;
}
if(valval)
{
Insert(t[k].l,val);
if(t[k].datelse
{
Insert(t[k].r,val);
if(t[k].datreturn;
}
有了旋转的操作之后, Treap T r e a p 的删除比二叉搜索树还要简单。因为 Treap T r e a p 满足堆性质,所以我们只需要把要删除的节点旋转到叶节点上,然后直接删除就可以了。具体的方法就是每次找到优先级较大的儿子,向与其相反的方向旋转,直到那个节点被旋转到了叶节点,然后直接删除。
旋转的复杂度为 O(1) O ( 1 ) ,删除最多进行 O(h) O ( h ) 次旋转,期望复杂度是 O(logn) O ( l o g n ) 。
ILV Remove(RI &k,RI val)
{
if(!k) return;
if(val==t[k].val)
{
if(t[k].cnt>1)//不只一个的话直接删掉一个
{
t[k].cnt--;Updata(k);//记得维护
return;
}
if(t[k].l||t[k].r)//有左右子树
{
if(!t[k].r||t[t[k].l].dat>t[t[k].r].dat)
zig(k),Remove(t[k].r,val);
else
zag(k),Remove(t[k].l,val);
Updata(k);
}
else k=0;
return;
}
if(valval) Remove(t[k].l,val);else Remove(t[k].r,val);
Updata(k);//记得维护
return;
}
维护平衡,时间复杂度 O(1) O ( 1 )
ILV zig(RI &k)//右旋
{
int q=t[k].l;
t[k].l=t[q].r;t[q].r=k;k=q;
Updata(t[k].r);Updata(k);
}
ILV zag(RI &k)//左旋
{
int q=t[k].r;
t[k].r=t[q].l;t[q].l=k;k=q;
Updata(t[k].l);Updata(k);
}
类似与 BST B S T ,我们可以利用之前维护的 size s i z e 来找,两个查询本质上是差不多的,倒过来即可,时间复杂度为 O(logn) O ( l o g n )
ILI Get_val(RI k,RI val)//根据分数查排名
{
if(!k) return k;
if(val==t[k].val) return t[t[k].l].size+1;//找到了就直接返回
if(valval) return Get_val(t[k].l,val);//在左边就去左边
return Get_val(t[k].r,val)+t[t[k].l].size+t[k].cnt;//否则就去右边,同时要加上左边的个数,因为要多出左边子树数量的序号
}
ILI Get_rank(RI k,RI rank)//根据排名查分数
{
if(!k) return k;
if(t[t[k].l].size>=rank) return Get_rank(t[k].l,rank);//超过了,去左边
if(t[t[k].l].size+t[k].cnt>=rank) return t[k].val;//刚刚好
return Get_rank(t[k].r,rank-t[t[k].l].size-t[k].cnt);//否则就去右边
}
跟 BST B S T 一样
ILI GetPre(RI val)
{
int ans=1,k=root;
while(k)
{
if(val==t[k].val)
{
if(t[k].l>0)
{
k=t[k].l;
while(t[k].r>0) k=t[k].r;//左子树上往右走
ans=k;
}
break;
}
if(t[k].val<val&&t[k].val>t[ans].val) ans=k;
k=valval?t[k].l:t[k].r;
}
return t[ans].val;
}
ILI GetNext(RI val)
{
int ans=2,k=root;
while(k)
{
if(val==t[k].val)
{
if(t[k].r>0)
{
k=t[k].r;
while(t[k].l>0) k=t[k].l;//右子树上往左走
ans=k;
}
break;
}
if(t[k].val>val&&t[k].valval) ans=k;
k=valval?t[k].l:t[k].r;
}
return t[ans].val;
}
#include
#include
#define ILI inline int
#define ILV inline void
#define RI register int
using namespace std;
const int Size=100010;
struct Treap
{
int l,r,val,dat,cnt,size;
}t[Size];
int tot,root,n,INF=0x3f3f3f3f;
ILI New(RI val)
{
t[++tot].val=val;
t[tot].dat=rand();
t[tot].cnt=t[tot].size=1;
return tot;
}
ILV Updata(RI k)
{
t[k].size=t[t[k].l].size+t[t[k].r].size+t[k].cnt;
return;
}
ILV Build()
{
New(-INF),New(INF);
root=1;t[1].r=2;
Updata(root);
}
ILI Get_val(RI k,RI val)
{
if(!k) return k;
if(val==t[k].val) return t[t[k].l].size+1;
if(valval) return Get_val(t[k].l,val);
return Get_val(t[k].r,val)+t[t[k].l].size+t[k].cnt;
}
ILI Get_rank(RI k,RI rank)
{
if(!k) return k;
if(t[t[k].l].size>=rank) return Get_rank(t[k].l,rank);
if(t[t[k].l].size+t[k].cnt>=rank) return t[k].val;
return Get_rank(t[k].r,rank-t[t[k].l].size-t[k].cnt);
}
ILV zig(RI &k)//右旋
{
int q=t[k].l;
t[k].l=t[q].r;t[q].r=k;k=q;
Updata(t[k].r);Updata(k);
}
ILV zag(RI &k)//左旋
{
int q=t[k].r;
t[k].r=t[q].l;t[q].l=k;k=q;
Updata(t[k].l);Updata(k);
}
ILV Insert(RI &k,RI val)
{
if(!k)
{
k=New(val);
return;
}
if(val==t[k].val)
{
t[k].cnt++;
Updata(k);
return;
}
if(valval)
{
Insert(t[k].l,val);
if(t[k].datelse
{
Insert(t[k].r,val);
if(t[k].datreturn;
}
ILI GetPre(RI val)
{
int ans=1,k=root;
while(k)
{
if(val==t[k].val)
{
if(t[k].l>0)
{
k=t[k].l;
while(t[k].r>0) k=t[k].r;
ans=k;
}
break;
}
if(t[k].val<val&&t[k].val>t[ans].val) ans=k;
k=valval?t[k].l:t[k].r;
}
return t[ans].val;
}
ILI GetNext(RI val)
{
int ans=2,k=root;
while(k)
{
if(val==t[k].val)
{
if(t[k].r>0)
{
k=t[k].r;
while(t[k].l>0) k=t[k].l;
ans=k;
}
break;
}
if(t[k].val>val&&t[k].valval) ans=k;
k=valval?t[k].l:t[k].r;
}
return t[ans].val;
}
ILV Remove(RI &k,RI val)
{
if(!k) return;
if(val==t[k].val)
{
if(t[k].cnt>1)
{
t[k].cnt--;Updata(k);
return;
}
if(t[k].l||t[k].r)
{
if(!t[k].r||t[t[k].l].dat>t[t[k].r].dat)
zig(k),Remove(t[k].r,val);
else
zag(k),Remove(t[k].l,val);
Updata(k);
}
else k=0;
return;
}
if(valval) Remove(t[k].l,val);else Remove(t[k].r,val);
Updata(k);
return;
}
signed main()
{
Build();
scanf("%d",&n);
while(n--)
{
int opt,x;
scanf("%d%d",&opt,&x);
if(opt==1) Insert(root,x);
if(opt==2) Remove(root,x);
if(opt==3) printf("%d\n",Get_val(root,x)-1);
if(opt==4) printf("%d\n",Get_rank(root,x+1));
if(opt==5) printf("%d\n",GetPre(x));
if(opt==6) printf("%d\n",GetNext(x));
}
}