分块
分块
分块其实是一种思想,而不是一种数据结构。
分块的基本思想是,通过对数据的适当划分,并在划分后的每一个块上预处理部分信息,从而较一般的暴力算法取得更优的时间复杂度。
分块的时间复杂度主要取决于分块的块长,一般可以通过均值不等式求出某个问题下的最优块长,以及相对应的时间复杂度。一般来说,我们取块数为 \(\sqrt{n}\) ,这样在最坏情况下,我们需要处理接近 \(\sqrt{n}\) 个整块,还要对长度为 \(\frac{2n}{\sqrt{n}} = 2\sqrt{n}\) 的零散块单独处理,总时间复杂度为 \(O(\sqrt{n})\)。这是一种根号算法。
分块是一种很灵活的理想,相较于树状数组与线段树,分块的优点是通用性更好,可以维护更多树状数组与线段树无法维护的信息。与线段树不同,块状数组维护的信息不要求满足结合律,也不需要一层一层传递标记。
预处理
具体地使用块状数组,我们先划定出每个块占据的范围
int len = sqrt(n);
for (int i = 1; i <= len; ++i) {
st[i] = n / len * (i - 1) + 1;
ed[i] = n / len * i;
}
但是,数组的当都并不一定是一个完全平方数,所以这样下来可能漏下一块,我们要把它们纳入最后一块中
ed[len] = n;
然后,我们为每个元素确定它所归属的块:
for (int i = 1; i <= len; ++i)
for (int j = st[i]; j <= ed[i]; ++j)
bel[j] = i;
最后,如果有必要,我们再预处理每一个块的大小
for (int i = 1; i <= len; ++i)
size[i] = ed[i] - st[i] + 1;
题目及具体用法
[P3372 【模板】线段树 1](P3372 【模板】线段树 1 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn))
数据只有 \(10^{5}\) ,可以用分块。同时我们需要一个 sum
数组来记录每一块的和,mark
数组来做标记
读入和预处理数据
for (int i = 1; i <= n; ++i)
A[i] = read();
for (int i = 1; i <= len; ++i)
for (int j = st[i]; j <= ed[i]; ++j)
sum[i] += A[j];
区间修改
首先是区间修改,当 x 与 y 在同一块中的时候,直接暴力修改原数组和 sum 数组:
if (bel[x] == bel[y]) {
for (int i = x; i <= y; ++i) {
A[i] += k;
sum[bel[i]] += k;
}
}
否则,先修改左右两边的零散区间:
for (int i = x; i <= en[bel[x]]; ++i) {
A[i] += k;
sum[bel[i]] += k;
}
for (int i = st[bel[y]]; i <= y; ++i) {
A[i] += k;
sum[bel[i]] += k;
}
然后对中间的整块打上标记:
for (int i = ble[x] + 1; i < bel[y]; ++i)
mark[i] += k;
区间查询
同样地,如果左右两边在同一块,直接暴力求和:
if (bel[x] == bel[y]) {
for (int i = x; i <= y; ++i)
s += A[i] + mark[bel[i]];
}
否则,暴力计算零散块:
for (int i = x; i <= ed[bel[x]]; ++i)
s += A[i] + mark[bel[i]]
for (int i = st[bel[y]]; i <= y; ++i)
s += A[i] + mark[bel[i]];
再处理整块:
for (int i = ble[x] + 1; i < bel[y]; ++i)
s += sum[i] + mark[i] * size[i];
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 5;
int n, m, len, st[N], ed[N], siz[N], bel[N];
ll mark[N], sum[N], A[N];
inline int read() {
int x = 0, f = 1;
char c = getchar();
while (!isdigit(c)) {
if (c == '-') f = -1;
c = getchar();
}
while (isdigit(c)) x = x * 10 + c - '0', c = getchar();
return x * f;
}
inline void add(int l, int r, int k) {
if (bel[l] == bel[r]) {
for (int i = l; i <= r; ++i) {
A[i] += k;
sum[bel[i]] += k;
}
} else {
for (int i = l; i <= ed[bel[l]]; ++i) {
A[i] += k;
sum[bel[i]] += k;
}
for (int i = st[bel[r]]; i <= r; ++i) {
A[i] += k;
sum[bel[i]] += k;
}
for (int i = bel[l] + 1; i < bel[r]; ++i) mark[i] += k;
}
}
inline ll query(int l, int r) {
ll ans = 0;
if (bel[l] == bel[r]) {
for (int i = l; i <= r; ++i) ans = ans + A[i] + mark[bel[i]];
return ans;
} else {
for (int i = l; i <= ed[bel[l]]; ++i) ans += A[i] + mark[bel[i]];
for (int i = st[bel[r]]; i <= r; ++i) ans += A[i] + mark[bel[i]];
for (int i = bel[l] + 1; i < bel[r]; ++i) ans += sum[i] + mark[i] * siz[i];
return ans;
}
}
int main() {
n = read(), m = read();
for (int i = 1; i <= n; ++i) A[i] = read();
len = sqrt(n);
for (int i = 1; i <= len; ++i) {
st[i] = n / len * (i - 1) + 1;
ed[i] = n / len * i;
}
ed[len] = n;
for (int i = 1; i <= len; ++i) siz[i] = ed[i] - st[i] + 1;
for (int i = 1; i <= len; ++i)
for (int j = st[i]; j <= ed[i]; ++j)
bel[j] = i;
for (int i = 1; i <= len; ++i)
for (int j = st[i]; j <= ed[i]; ++j)
sum[i] += A[j];
int opt, x, y, k;
while (m--) {
opt = read();
x = read(), y = read();
if (opt == 1) {
k = read();
add(x, y, k);
} else printf("%lld\n", query(x, y));
}
system("pause");
return 0;
}
[P2801 教主的魔法](P2801 教主的魔法 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn))
零散块处理起来要容易些,但是我们如何对整块整体处理呢?实际上我们可以维护整块排序后的数组,然后对整块进行询问时直接二分查找即可。
注意,我们每次对零散块进行单独修改的时候,都需要重新排序更新后的数组,这听起来很暴力,但是每个块相对较少,也可以接受,总时间复杂度为 \(O(q \sqrt{n}\log \sqrt{n})\)
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
int n, m, len, bel[N], siz[N], st[N], ed[N], A[N], mark[N];
vector <int> v[N];
inline int read() {
int x = 0, f = 1;
char c = getchar();
while (!isdigit(c)) {
if (c == '-') f = -1;
c = getchar();
}
while (isdigit(c)) x = x * 10 + c - '0', c = getchar();
return x * f;
}
inline void block_init() {
len = sqrt(n);
for (int i = 1; i <= len; ++i) {
st[i] = n / len * (i - 1) + 1;
ed[i] = n / len * i;
}
ed[len] = n;
for (int i = 1; i <= len; ++i) {
for (int j = st[i]; j <= ed[i]; ++j) {
bel[j] = i;
v[i].push_back(A[j]);
}
sort(v[i].begin(), v[i].end());
}
for (int i = 1; i <= len; ++i) siz[i] = ed[i] - st[i] + 1;
}
inline void add(int l, int r, int k) {
if (bel[l] == bel[r]) {
for (int i = l; i <= r; ++i) A[i] += k;
for (int i = 0; i <= v[bel[l]].size(); ++i) v[bel[l]][i] = A[st[bel[l]] + i];
sort(v[bel[l]].begin(), v[bel[l]].end());
} else {
for (int i = l; i <= ed[bel[l]]; ++i) A[i] += k;
for (int i = st[bel[r]]; i <= r; ++i) A[i] += k;
for (int i = 0; i <= v[bel[l]].size(); ++i) v[bel[l]][i] = A[st[bel[l]] + i];
sort(v[bel[l]].begin(), v[bel[l]].end());
for (int i = 0; i <= v[bel[r]].size(); ++i) v[bel[r]][i] = A[st[bel[r]] + i];
sort(v[bel[r]].begin(), v[bel[r]].end());
for (int i = bel[l] + 1; i < bel[r]; ++i) mark[i] += k;
}
}
inline int query(int l, int r, int k) {
int ans = 0;
if (bel[l] == bel[r]) {
for (int i = l; i <= r; ++i) {
if (A[i] + mark[bel[i]] >= k) ++ans;
}
return ans;
} else {
for (int i = l; i <= ed[bel[l]]; ++i) if (A[i] + mark[bel[i]] >= k) ++ans;
for (int i = st[bel[r]]; i <= r; ++i) if (A[i] + mark[bel[i]] >= k) ++ans;
for (int i = bel[l] + 1; i < bel[r]; ++i) {
ans += v[i].end() - lower_bound(v[i].begin(), v[i].end(), k - mark[i]);
}
return ans;
}
}
int main() {
n = read(), m = read();
for (int i = 1; i <= n; ++i) A[i] = read();
block_init();
char opt;
int x, y, k;
while (m--) {
cin >> opt >> x >> y >> k;
//scanf("%c%d%d%d", &opt, &x, &y, &k);
if (opt == 'M') {
add(x, y, k);
} else {
printf("%d\n", query(x, y, k));
}
}
//system("pause");
return 0;
}
分块入门模板题单
[入门题单](题库 - LibreOJ (loj.ac))