专栏文章
常数访问块状链表(省流:带插分块)
算法·理论参与者 1已保存评论 0
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 0 条
- 当前快照
- 1 份
- 快照标识符
- @mimy2tyc
- 此快照首次捕获于
- 2025/12/01 17:26 3 个月前
- 此快照最后确认于
- 2025/12/01 17:26 3 个月前
前言
因为长度是严格的,没有块链的许多优良性质,所以适用范围不广。
原理
例题
有数组 ,最初为空。在线地实现以下操作:
- 求 。
- 将 改为 。
- 在 前插入 。
如果 ,则表示是从末尾插入。 - 删除 。
下标从 开始,操作数量 。
实际上就是三个操作:访问,插入,删除。访问可以返回引用,实现操作 。
我们将数组分为若干个长度相等的块,每个块用
为了简单,做以下约定:
deque 维护。为了简单,做以下约定:
- 块长定为 。
那么,第 个元素在第 块的第 个。 - 最后一个块可以有任何不大于 的长度。
vector<deque<int>> a {{}}; // 开始要有一个块
由于我们知道了第 个元素的位置,访问自然可以做到 。
CPPint& get(int p) {
return a[p / B][p % B];
}
通过类似的方式,插入、删除时易找到元素位置。可是,如何维护长度相等的性质呢?
插入
插入一个元素到对应块后,它的长度可能是 。
插入平衡(块 )
- 如果 是最后一个块,将末尾元素分裂为新的块。
- 否则,将末尾元素移动到块 的开头。
如果块 为 个元素,对 递归执行平衡操作。

void ins(int p, int x) {
int i = p / B;
a[i].insert(a[i].begin() + p % B, x);
// 平衡
for (int e = a.size(); i < e; ++i) {
if (i == int(a.size() - 1))
if(a[i].size() > B) a.emplace_back();
else break;
a[i + 1].push_front(a[i].back());
a[i].pop_back();
}
}
删除
删除一个元素后,非末尾块 的长度可能是 。
此时,将块 的开头移动到块 的末尾。
如果块 是非末尾块且长度为 ,对下一个块递归执行。
此时,将块 的开头移动到块 的末尾。
如果块 是非末尾块且长度为 ,对下一个块递归执行。

void del(int p) {
int i = p / B;
a[i].erase(a[i].begin() + p % B);
// 平衡
for (int e = a.size() - 1; i < e; ++i) {
a[i].push_back(a[i + 1].front());
a[i + 1].pop_front();
}
}
实现
CPP#include <bits/stdc++.h>
#define fo(i,l,r) for(int i=(l),E##i=(r);i<=E##i;++i)
#define N 100005
#define B 350
using namespace std;
struct Arr {
vector<deque<int>> a {{}}; // 预先设置一个块
int& get(int p) {
return a[p / B][p % B];
}
void ins(int p, int x) {
int i = p / B;
a[i].insert(a[i].begin() + p % B, x);
// 平衡
for (int e = a.size(); i < e; ++i) {
if (i == int(a.size() - 1))
if(a[i].size() > B) a.emplace_back();
else break;
a[i + 1].push_front(a[i].back());
a[i].pop_back();
}
}
void del(int p) {
int i = p / B;
a[i].erase(a[i].begin() + p % B);
// 平衡
for (int e = a.size() - 1; i < e; ++i) {
a[i].push_back(a[i + 1].front());
a[i + 1].pop_front();
}
}
};
//int test() {
// Arr a;
// fo (i, 0, 799) a.ins(0, i);
// fo (i, 1, 20) a.del(20);
// fo (i, 1, 20) a.del(720);
// fo (i, 0, 759) cout << a.get(i) << endl;
//}
// 1 询问 2 修改 3 插入 4 删除
int main() {
int m, op, p, x;
Arr a;
cin >> m;
up (i, 1, m) {
cin >> op >> p;
switch (op) {
case 1: cout << a.get(p - 1) << endl; break;
case 2: cin >> x; a.get(p - 1) = x; break;
case 3: cin >> x; a.ins(p - 1, x); break;
case 4: a.del(p - 1);
}
}
}
后记
分裂合并明天不用来了。舒服。
关于可持久化的尝试
在写可持久化块状链表:
可持久化块状数组(分块可持久化)
可持久化块状数组(分块可持久化)
每个版本维护一个各块长度前缀和的数组(其实就是块头的索引),似乎也可以做到 的随机访问,而且好写(逃)
发现使用哈希表记录每个索引对应的块也可以,但是可持久化哈希表又是另一个问题。
以上。
相关推荐
评论
共 0 条评论,欢迎与作者交流。
正在加载评论...