一.一道模板题.
题目:BZOJ3224.
题目大意:在序列上维护一个数据结构支持:
1.插入数
x
x
x.
2.删除数
x
x
x(只删一个).
3.查找
x
x
x的最小排名.
4.查找排名为
x
x
x的数.
5.查找
x
x
x的前驱.
6.查找
x
x
x的后继.
1
≤
n
≤
1
0
5
1\leq n\leq 10^5
1≤n≤105.
在各类平衡树的学习(一)——Splay中,我们用splay解决了这个问题,接下来我们将会用另一种平衡树数据结构Treap解决这个问题.
建议先去看各类平衡树的学习(一)——二叉查找树BST.
二.Treap维护的信息.
Treap与普通BST存的信息基本一致,只是多存了一个用于维护平衡的随机出来的点权 v v v.
代码如下:
struct tree{
int s[2],x,v,cnt,siz;
}tr[N+9];
int cn,rot;
三.Treap的性质.
Treap一个节点上存了两个附加值,一个是 x x x,我们称之为键值;一个是 v v v,我们称之为点权.其中点权是随机出来的.
与其他平衡树一样,Treap上键值满足BST性质;而与其他平衡树不同的是,Treap还需要使得点权满足堆性质.
为什么还要维护一个点权满足堆性质呢?因为键值是给定的,它可以特意构造数据把BST卡成链;而我们再随机出来的一个点权需要满足堆性质,既不会与原来键值满足BST性质产生冲突,也可以使得这棵BST的深度变得随机不会被卡了.
实际上,随机出来的Treap的深度期望为
O
(
log
n
)
O(\log n)
O(logn),实践中一般在
log
n
\log n
logn的
1.5
1.5
1.5到
2
2
2倍左右,所以Treap就可以放心大胆的用了.
四.Treap的旋转.
虽然外面上面说了点权 v v v要满足堆性质就可以使Treap变得平衡,但是该怎么样在点权 v v v破坏了堆性质时,通过一些方式使这个破坏性质的节点满足堆性质呢?
当然是通过旋转.
我们分别称左旋
k
k
k为
z
i
g
(
k
)
zig(k)
zig(k)和右旋
k
k
k为
z
a
g
(
k
)
zag(k)
zag(k),形式分别如下:
分别可以写成:
void Zig(int &k){
int s=tr[k].s[0];
tr[k].s[0]=tr[s].s[1];tr[s].s[1]=k;
k=s;
Pushup(tr[k].s[1]);Pushup(k);
}
void Zag(int &k){
int s=tr[k].s[1];
tr[k].s[1]=tr[s].s[0];tr[s].s[0]=k;
k=s;
Pushup(tr[k].s[0]);Pushup(k);
}
当然也可以合并成一个函数:
void Rotate(int &k,int d){ //d为0左旋,为1右旋
int s=tr[k].s[d];
tr[k].s[d]=tr[s].s[d^1];tr[s].s[d^1]=k;k=s;
Pushup(tr[k].s[d^1]);Pushup(k);
}
五.Treap的插入及建树.
Treap主要只有插入和删除是与普通BST不一样的,其它都是完全相同的代码,所以接下来只会介绍插入和删除.
插入只需要从根开始,大力往下找位置,如果找到相同权值的节点 k k k直接给 t r [ k ] . c n t tr[k].cnt tr[k].cnt加 1 1 1并返回;否则会到一个空节点,然后新建一个节点赋一个随机点权,然后返回.退出每一层递归的过程中注意要判定这一层是否满足堆性质,不满足需要一次旋转.
代码如下:
void Pushup(int k){tr[k].siz=tr[tr[k].s[0]].siz+tr[tr[k].s[1]].siz+tr[k].cnt;}
int New_node(int x){
tr[++cn]=tree();
tr[cn].x=x;tr[cn].v=rand()%2333333+1; //注意不能生成0
tr[cn].cnt=tr[cn].siz=1;
return cn;
}
void Insert(int x,int &k=rot){
if (!k) {k=New_node(x);return;}
if (tr[k].x==x) {++tr[k].cnt;Pushup(k);return;}
Insert(x,tr[k].s[x>tr[k].x]);
if (tr[k].v<tr[tr[k].s[x>tr[k].x]].v) Rotate(k,x>tr[k].x);
Pushup(k);
}
接下来是Treap的建树,第一个节点的插入可以直接生成,但第二个节点的插入可能需要一次旋转,所以我们还是安安心心写一个插入好了.
代码如下:
void Build(){cn=0;rot=New_node(-INF);Insert(INF);}
六.Treap的删除.
回忆一下BST的删除,我们发现如果那样写又臭又长,所以我们考虑如何利用旋转这一条件.
类似于二叉堆的删除,我们直接把要删除的那个节点旋转到叶子直接删除即可.
代码如下:
void Erase(int x,int &k=rot){
if (!k) return;
if (x==tr[k].x){
--tr[k].cnt;Pushup(k);
if (tr[k].cnt<0) tr[k].cnt=0;
if (tr[k].cnt) return;
if (tr[k].s[0]||tr[k].s[1]){
if (!tr[k].s[0]||tr[tr[k].s[0]].v<tr[tr[k].s[1]].v)
Rotate(k,1),Erase(x,tr[k].s[0]);
else Rotate(k,0),Erase(x,tr[k].s[1]);
}else k=0;
return;
}
Erase(x,tr[k].s[x>tr[k].x]);
Pushup(k);
}
七.随机数生成的常数优化.
我们知道C++内置的随机数生成函数 r a n d ( ) rand() rand()是非常慢且容易生成 0 0 0,所以考虑优化随机数生成的常数问题.
考虑随便选一个种子 s e e d seed seed,每次乘以一个大数并且对一个数取模后加 1 1 1避免出现 0 0 0.
代码如下:
int Rand(){return (sed=sed*19491001)%998244353+1;}
八.完整代码.
普通平衡树代码如下:
#include<bits/stdc++.h>
using namespace std;
#define Abigail inline void
typedef long long LL;
const int N=100000,INF=(1<<31)-1;
struct tree{
int s[2],x,v,cnt,siz;
}tr[N+9];
int cn,rot;
unsigned int sed=2333;
void Pushup(int k){tr[k].siz=tr[tr[k].s[0]].siz+tr[tr[k].s[1]].siz+tr[k].cnt;}
int Rand(){return (sed=sed*19491001)%998244353+1;}
void Rotate(int &k,int d){ //d为0左旋,为1右旋
int s=tr[k].s[d];
tr[k].s[d]=tr[s].s[d^1];tr[s].s[d^1]=k;k=s;
Pushup(tr[k].s[d^1]);Pushup(k);
}
int Find_val(int x){
int k=rot;
for (;k&&tr[k].x^x;k=tr[k].s[x>tr[k].x]);
return k?k:-1;
}
int New_node(int x){
tr[++cn]=tree();
tr[cn].x=x;tr[cn].v=Rand();
tr[cn].cnt=tr[cn].siz=1;
return cn;
}
void Insert(int x,int &k=rot){
if (!k) {k=New_node(x);return;}
if (tr[k].x==x) {++tr[k].cnt;Pushup(k);return;}
Insert(x,tr[k].s[x>tr[k].x]);
if (tr[k].v<tr[tr[k].s[x>tr[k].x]].v) Rotate(k,x>tr[k].x);
Pushup(k);
}
void Build(){cn=0;rot=New_node(-INF);Insert(INF);}
int Query_rank(int x){
int res=0,k=rot;
for (;k&&tr[k].x^x;k=tr[k].s[x>tr[k].x])
if (x>tr[k].x) res+=tr[tr[k].s[0]].siz+tr[k].cnt;
if (k) res+=tr[tr[k].s[0]].siz;
return res;
}
int Find_rank(int p){
++p;
int k=rot;
while (2333)
if (p>tr[tr[k].s[0]].siz+tr[k].cnt) p-=tr[tr[k].s[0]].siz+tr[k].cnt,k=tr[k].s[1];
else if (p<=tr[tr[k].s[0]].siz) k=tr[k].s[0];
else return k;
}
int Lower(int x){
int res=1,k=rot;
for (;k&&tr[k].x^x;k=tr[k].s[x>tr[k].x])
if (tr[k].x<x&&tr[k].x>tr[res].x) res=k;
for (k=tr[k].s[0];k;k=tr[k].s[1])
if (tr[k].x<x&&tr[k].x>tr[res].x) res=k;
return res;
}
int Upper(int x){
int res=2,k=rot;
for (;k&&tr[k].x^x;k=tr[k].s[x>tr[k].x])
if (tr[k].x>x&&tr[k].x<tr[res].x) res=k;
for (k=tr[k].s[1];k;k=tr[k].s[0])
if (tr[k].x>x&&tr[k].x<tr[res].x) res=k;
return res;
}
void Erase(int x,int &k=rot){
if (!k) return;
if (x==tr[k].x){
--tr[k].cnt;Pushup(k);
if (tr[k].cnt<0) tr[k].cnt=0;
if (tr[k].cnt) return;
if (tr[k].s[0]||tr[k].s[1]){
if (!tr[k].s[0]||tr[tr[k].s[0]].v<tr[tr[k].s[1]].v)
Rotate(k,1),Erase(x,tr[k].s[0]);
else Rotate(k,0),Erase(x,tr[k].s[1]);
}else k=0;
return;
}
Erase(x,tr[k].s[x>tr[k].x]);
Pushup(k);
}
Abigail getans(){
int opt,x,n;
scanf("%d",&n);
Build();
while (n--){
scanf("%d%d",&opt,&x);
switch (opt){
case 1:
Insert(x);
break;
case 2:
Erase(x);
break;
case 3:
printf("%d\n",Query_rank(x));
break;
case 4:
printf("%d\n",tr[Find_rank(x)].x);
break;
case 5:
printf("%d\n",tr[Lower(x)].x);
break;
case 6:
printf("%d\n",tr[Upper(x)].x);
break;
}
}
}
int main(){
getans();
return 0;
}