(看见NOI大纲中有这个没听说过的东西,于是赶紧了解了一下)
链表可以快速插入,但随机访问的复杂度是
O
(
n
)
O(n)
O(n) 的,相比于
O
(
log
n
)
O(\log n)
O(logn) 的平衡树很慢,就遭受到了各位大佬的嫌弃。于是为了给链表正名,我们尝试优化它。(以下数字为节点编号)
0
→
1
→
2
→
3
→
4
→
5
→
6
→
7
→
8
0\rightarrow1\rightarrow2\rightarrow3\rightarrow4\rightarrow5\rightarrow6\rightarrow7\rightarrow8
0→1→2→3→4→5→6→7→8
结合倍增的思想,我们尝试加几层链表辅助查找
0
→
2
→
4
→
6
→
8
0\rightarrow2\rightarrow4\rightarrow6\rightarrow8
0→2→4→6→8
0
→
4
→
8
0\rightarrow4\rightarrow8
0→4→8
0
→
8
0\rightarrow8
0→8
这样显然可以看出查找的复杂度是
O
(
log
n
)
O(\log n)
O(logn) 。
但插入删除的时候,如果只插入删除最底层,结构会发生变化,导致复杂度退化。插入上层太多也不行。如何衡量插入几层呢?
考虑随机化。假设我们把一个节点插入第
i
i
i 层时,以
1
p
\frac1p
p1 的概率同时把这个节点插入第
i
+
1
i+1
i+1 层。
第
i
+
1
i+1
i+1 层的两个数之间期望有
p
p
p 个第
i
i
i 层的数,共有
log
p
n
\log_pn
logpn 层,单次操作复杂度
O
(
p
log
p
n
)
O(p\log_pn)
O(plogpn),
p
p
p 取
e
e
e 的时候最优。当然这些复杂度都是期望的,普通的二叉搜索树还是单次操作期望
O
(
log
n
)
O(\log n)
O(logn) 的呢,实际上很容易T飞。所以还要证明消耗时间高的概率很小。第
i
+
1
i+1
i+1 层两个数之间在第
i
i
i 层插入一个数且不会进入第
i
+
1
i+1
i+1 层的概率是
1
−
1
p
1-\frac1p
1−p1 ,所以操作时每一层遍历
k
k
k 个节点的概率是
(
1
−
1
p
)
k
(1-\frac1p)^k
(1−p1)k,所以单次操作总遍历节点数为
k
k
k 的概率为
f
(
k
)
=
(
1
−
1
p
)
k
(
k
+
log
p
n
−
1
log
p
n
−
1
)
f(k)=(1-\frac1p)^k\binom{k+\log_pn-1}{\log_pn-1}
f(k)=(1−p1)k(logpn−1k+logpn−1) ,
f
(
k
+
1
)
=
(
1
−
1
p
)
k
+
log
p
n
k
+
1
f
(
k
)
f(k+1)=(1-\frac1p)\frac{k+\log_pn}{k+1}f(k)
f(k+1)=(1−p1)k+1k+logpnf(k) ,如果
p
p
p 取
2
2
2 可以发现当
k
k
k 超过层数后
f
(
k
)
f(k)
f(k) 在指数级衰减,
k
k
k 较大的概率可以忽视掉 。
这样的数据结构被命名为跳跃表,容易理解,代码也比较短,可以支持
O
(
log
n
)
O(\log n)
O(logn) 区间查询、区间修改、插入、删除、翻转、合并、分裂,还能可持久化、套其它数据结构,总之和非旋treap的作用极度重合,速度也差不多,唯一优势就是加底层优化后会比较快(所以在OI中并没有什么用)。
以下是洛谷 P3369 【模板】普通平衡树 的代码
#include<bits/stdc++.h>
using namespace std;
const int P=RAND_MAX/exp(1),inf=1e9;
int read(){
int x=0,f=0;char c;
while(!isdigit(c=getchar()))f|=c=='-';
for(;isdigit(c);c=getchar())x=x*10+c-'0';
return f?-x:x;
}
const int N=13;
struct lis{
int a,s;
lis*r,*d;//r指当前层链表的下一个,d指对应节点的下一层
void up(){s=0;
for(lis*i=d;i!=r->d;i=i->r)s+=i->s;
}
lis*ins(int x){
if(r->a<x)return r->ins(x);
if(!d)return r=new lis{x,1,r,0};
lis*p=d->ins(x);up();
if(!p||(rand()>P))return 0;
r=new lis{x,1,r,p};
r->up();up();
return r;
}
void del(int x){
if(s<x)r->del(x-s);
else if(s>x)d->del(x),up();
else if(!d)r=r->r;
else{
d->del(x);
r=r->r;up();
}
}
int ask(int x){
return r->a<x?s+r->ask(x):d?d->ask(x):1;
}
int kth(int x){
return x?s>x?d->kth(x):r->kth(x-s):a;
}
};
int main(){
srand(time(0));
lis*L=0,*R=0;
for(int i=N;i;i--){
R=new lis{inf,1,0,R};
L=new lis{-inf,1,R,L};
}
for(int n=read(),t,x;n;n--){
t=read();x=read();
if(t==1)L->ins(x);
if(t==2)L->del(L->ask(x));
if(t==3)cout<<L->ask(x);
if(t==4)cout<<L->kth(x);
if(t==5)cout<<L->kth(L->ask(x)-1);
if(t==6)cout<<L->kth(L->ask(x+1));
if(t>2)cout<<'\n';
}
return 0;
}