块状链表 学习笔记
感谢大佬 @w33z8kqrqk8zzzx33!
用块状链表实现平衡树,是怎么回事呢?快来跟小编看看吧~
前置知识
vector
的部分用法
vector
就是变长数组。由于 STL 制作者们八仙过海,所以它的一些操作常数极小(相比于普通数组)。
首先注意,vec
的第一个位置下标是 0
;而且大多函数都是左闭右开的(就像 sort
一样,你要写 sort(a+1,a+n+1)
而不是 sort(a+1,a+n)
,就是因为 sort(l,r)
的范围是 \([l,r)\) 即 \([l,r-1]\))。
-
vec.begin()
它的第一个位置(如果你想知道vec
第一个位置的数字是什么,你可以用vec.front()
或vec[0]
)。 -
vec.end()
它的最后一个位置还要后一位(所以实际的最后一位是--vec.end()
)(与vec.front()
同理,最后一个位置的数字是vec.back()
)。 -
vec[wei]
第wei
个位置的数字。 -
vec.size()
它有多少个元素。 -
vec.emplace_back(x)
在它的最后放一个新数字x
(和push_back
是一个东西),\(O(1)\)。 -
vec.emplace(wei,x)
在它的wei
这个位置前面插入一个新位置,新位置的数字是x
(和insert
是一个东西),极小常数的 \(O(n)\)。 -
vec.erase(wei)
删除wei
这个位置,极小常数的 \(O(n)\)。 -
lower_bound(vec.begin(),vec.end(),x)
查询整个vec
第一个大于等于x
的数字的位置是什么。这个函数也是左闭右开的,但是因为vec.end()
刚好指向最后一位的下一位,所以可以视作整个vec
,\(O(\log n)\)。 -
upper_bound(vec.begin(),vec.end(),x)
查询整个vec
第一个大于x
的数字的位置是什么。和上面同理。
平衡树题意
维护一个多重集合,需支持下列操作:
- 插入或删除一个数 \(x\);
- 查询 \(x\) 排名;
- 查询排名为 \(x\) 的数字是什么;
- 查询 \(x\) 的前驱/后继。
块状链表
我们想到一个暴力做法:维护一个 vec
,使得里面的数字都是从小到大排好序的。
对于四个操作:
- 二分 \(x\) 应该在的位置,暴力插入/删除。
- 二分 \(x\) 应该在的位置。
cout<<vec[x]
。- 二分 \(x\) 应该在的位置。
瓶颈显然在于操作 1 ,是小常数 \(O(n)\)。
那么考虑分块。为了方便,设 \(B=\sqrt n\),分成了 B
个大小为 B
的块。
然后二分 \(x\) 的位置,就可以转为另一个方法:
依次遍历每个块,看一下块尾的元素是否小于 \(x\)。如果否,则 \(x\) 的位置就在这个块中,在块内二分即可。这是 \(O(B)\) 的哟~
暴力插入/删除的话,也变成了小常数 \(O(B)\)(实测跑的飞快,就像发疯了的 \(O(\log n)\) 一样!)。
但是如果我们不停地在同一个块进行插入,这个块变得很大,会退化成 \(O(n)\)。
解决方案是,如果有一个块的块长大于 \(B\) 了,那么就把它们掰成两个 \(\frac{B}{2}\) 的块。
这样保证时间复杂度了,但是还有一些细节:
- 为了仍能保证块的大小顺序(即把所有块合成一起时,仍是从小到大排序的),我们需要用链表把所有的块串起来,这样掰开块的时候就是 \(O(1)\) 的了。
- 因为链表不支持定点访问,操作 3 的方法要改变了。设立当前排名 \(cnt\),我们一次遍历每一个块,就让 \(cnt\) 增加块的大小。如果 \(cnt\ge x\) 了,代表就在当前块中,块内二分即可。
- 一开始把数列加进去的时候,是 \(\frac{B}{2}\) 个为一组,放进块中。
代码
题目 【模板】普通平衡树
链表可以直接用 vector
代替,也就是 vec
套 vec
!!1
代码非常短,于是块状链表也被称作 “五分钟平衡树”。
#include<bits/stdc++.h>
#define rep(i,x,y) for(int i=x;i<=y;++i)
#define ltor(id) kzlb[id].begin(),kzlb[id].end()
#define iter vector <int> ::iterator
#define pb emplace_back
using namespace std;
const int n7=101234,inf=1e7+1;
int T,B;
vector < vector <int> > kzlb;
int rd(){
int shu=0;bool fu=0;char ch=getchar();
while( !isdigit(ch) ){if(ch=='-')fu=1;ch=getchar();}
while( isdigit(ch) )shu=(shu<<1)+(shu<<3)+ch-'0',ch=getchar();
return fu?-shu:shu;
}
void wt(int z){
if(z<0)putchar('-'),z=-z;
if(z>9)wt(z/10);
putchar(z%10+'0');
}
int ffind(int x){
rep(i,0,kzlb.size()-1){
if(kzlb[i].back()>=x)return i;
}
}
void isert(int x){
int id=ffind(x);
kzlb[id].emplace(lower_bound(ltor(id),x),x);
if(kzlb[id].size()>B){
kzlb.emplace(kzlb.begin()+id+1,kzlb[id].begin()+B/2, kzlb[id].end() );
kzlb[id].erase(kzlb[id].begin()+B/2, kzlb[id].end() );
}
}
void erase(int x){
int id=ffind(x);
kzlb[id].erase( lower_bound(ltor(id),x) );
if( !kzlb[id].size() )kzlb.erase(kzlb.begin()+id);
}
int Qkth(int x){
rep(i,0,kzlb.size()-1){
if(kzlb[i].size()>=x)return kzlb[i][x-1];
else x-=kzlb[i].size();
}
}
int Qnum(int x){
int cnt=0;
rep(i,0,kzlb.size()-1){
if(kzlb[i].back()>=x){
cnt+=lower_bound(ltor(i),x)-kzlb[i].begin();
return cnt+1;
}
else cnt+=kzlb[i].size();
}
}
int Qpre(int x){
int id=ffind(x);
iter wei=lower_bound(ltor(id),x);
if( wei==kzlb[id].begin() )return kzlb[id-1].back();
else return *--wei;
}
int Qnxt(int x){
int id=ffind(x+1);
iter wei=upper_bound(ltor(id),x);
//左闭右开,所以wei=end表示找不到
if( wei==kzlb[id].end() )return kzlb[id+1].front();
else return *wei;
}
int main(){
kzlb.pb();
kzlb.back().pb(inf);
T=rd(),B=sqrt(T);
while(T--){
int sys=rd(),x=rd();
if(sys==1)isert(x);
if(sys==2)erase(x);
if(sys==3)wt( Qnum(x) ),putchar('\n');
if(sys==4)wt( Qkth(x) ),putchar('\n');
if(sys==5)wt( Qpre(x) ),putchar('\n');
if(sys==6)wt( Qnxt(x) ),putchar('\n');
}
return 0;
}