分块和普通莫队入门
分块
分块是一种数据结构,其核心就是暴力。和线段树和树状数组一样,它可以支持区间修改,单点修改,区间查询,单点查询。
总体来讲,可以总结为大段维护,小段暴力。
下面具体讲一下它的实施。
首先,我们了一个数组,假设这个数组的大小是 \(n\),那么我们就把它分成 \(\lceil \sqrt{n} \rceil\) 段,其中 \(\lfloor \sqrt{n} \rfloor\) 段的大小都是 \(\sqrt{n}\),剩下最后一个段的大小就是 \(n - \lfloor \sqrt{n} \rfloor \times \sqrt{n}\)。
就像下面这张图一样。(画的太丑乐)
分块基本上都可以做到线段树能做的,所以就拿线段树的板子来写一下。
P3372 【模板】线段树 1
对于每一个段,我们可以单独维护两个数组,一个表示这个段里面的和,另一个表示这个块的延迟标记。
我们可以把 \([l, r]\) 这个区间看成这样子。
- 查询:
那么橙色部分就可以直接维护,让我们的答案直接加上这段的和以及延迟标记乘上块长即可。
而对于我用绿色圈出来的部分就没有办法了,只能暴力算了。 - 修改
一样的,对于橙色部分,我们直接让这一段的延迟标记直接加。
而绿色部分也暴力维护即可。
然后为了方便写,可以维护一些东西,比如这个块是哪一个区间,大小是多少,所有下标所归属的块的标号等等。
时间复杂度为 \(O(n\sqrt{n})\)
int n, m;
i64 a[N], sum[N], add[N], z;
int l[N], r[N], belong[N], siz[N];
int op, x, y;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr); std::cout.tie(nullptr);
std::cin >> n >> m;
for (int i = 1; i <= n; i++) {
std::cin >> a[i];
}
int k = std::sqrt(n), kn = 1;
for (int i = 1; ; i += k, kn++) {
if (i + k - 1 >= n) {
l[kn] = i;
r[kn] = n;
break;
} else {
l[kn] = i;
r[kn] = i + k - 1;
}
}
for (int i = 1; i <= kn; i++) {
for (int j = l[i]; j <= r[i]; j++) {
sum[i] += a[j];
belong[j] = i;
}
siz[i] = r[i] - l[i] + 1;
}
while (m--) {
std::cin >> op >> x >> y;
if (op == 1) {
std::cin >> z;
if (belong[x] == belong[y]) {
for (int i = x; i <= y; i++) {
a[i] += z;
sum[belong[x]] += z;
}
} else {
for (int i = x; i <= r[belong[x]]; i++) {
a[i] += z;
sum[belong[x]] += z;
}
for (int i = belong[x] + 1; i <= belong[y] - 1; i++) {
add[i] += z;
}
for (int i = l[belong[y]]; i <= y; i++) {
a[i] += z;
sum[belong[y]] += z;
}
}
} else {
i64 ans = 0;
if (belong[x] == belong[y]) {
for (int i = x; i <= y; i++) {
ans += a[i] + add[belong[x]];
}
} else {
for (int i = x; i <= r[belong[x]]; i++) {
ans += a[i] + add[belong[x]];
}
for (int i = belong[x] + 1; i <= belong[y] - 1; i++) {
ans += sum[i] + add[i] * siz[i];
}
for (int i = l[belong[y]]; i <= y; i++) {
ans += a[i] + add[belong[y]];
}
}
std::cout << ans << "\n";
}
}
return 0;
}
莫队
莫队就是一个离线处理多个询问的算法,因为是离线的,所以普通的莫队不支持修改。然后莫队算法最精髓的东西就是它对询问进行分块。
首先把询问给读入进来,按照字典序排一下序,然后根据这个询问的区间一步一步的拓展区域,这就是莫队的最精髓的东西。然后如果这个拓展的过程可以 \(O(1)\),那么时间复杂度就是 \(O(n\sqrt{n})\),这个东西 OI-Wiki 上有证明,但是我咕咕咕。
然后就是一个拓展区域的问题,应该怎么拓展就是一个问题。
OI-Wiki 写的很好,所以我就不写了,这个背一个形式应该就可以吧,我觉得背 l--, r++, l++, r--
就很好。
这个题还要用一点排列组合:
但是先咕咕咕者。
好,莫队咕完了。