本文来自我的博客 块状链表详解 + 例题
引入
块状链表,顾名思义,就是把分块和链表结合起来的神奇数据结构。
-
分块区间操作复杂度优秀,但是不能支持
插入/删除
操作。 -
链表单点插入删除复杂度优秀,但是不能支持大规模的区间操作。
但是两者相结合,就会变得非常无敌。
块状链表思想
块状链表的实现原理根本上就是保证每个块的大小稳定在 n \sqrt{n} n 级别,从而保证块数也为根号级别。但是插入数据之后,可能会出现某个快过大的情况。当块长 > 2 n > 2 \sqrt{n} >2n 时,可以将块分裂成两个,保证每块大小稳定在 [ n , 2 n ] [\sqrt{n}, 2\sqrt{n}] [n,2n]。这样分裂的复杂度是单根号的。
这样保证了块数为根号级别,定位一个数的位置也可以在 O ( n ) O(\sqrt n) O(n) 复杂度内解决。只要能定位数所在的位置,就可以用 O ( 1 ) O(1) O(1) 复杂度完成插入和删除,其他操作都跟分块复杂度相同。
块状链表如何维护数据
拿 Luogu 普通平衡树模板 做例题。
要支持在 O ( n ) O(\sqrt n) O(n) 复杂度内维护一个有序序列。
首先需要建一个双链表(某些题可以单链表),作为外层数据结构。
然后再每个链表里面维护一个小链表(可以用 vector
实现),来维护当前块。另外,由于后续操作写成 (p -> vector).lower_bound((p -> vector).begin(), (p -> vector).end(), x)
这样的形式不是很美观,也可以在块链类内部提前封装类似函数。
struct node {
// 构建块状链表类
node *next, *pre;
vector<int> v;
node() {
next = pre = NULL; }
void insert(int x) {
v.emplace_back(x); }
int vsize() {
return v.size(); }
int back() {
return v[v.size() - 1]; }
int front() {
return v[0]; }
vector<int>::iterator vbegin() {
return v.begin(); }
vector<int>::iterator vend() {
return v.end(); }
vector<int>::iterator lower(int x) {
return lower_bound(v.begin(), v.end(), x); }
vector<int>::iterator upper(int x) {
return upper_bound(v.begin