此博客仅供分块思想入门使用,如果需要进一步地了解分块,请前往笔者的 这篇博文
基本概念
分块是一种经过优化的暴力算法,它将一个长度为 \(n\) 的数组分成尽量平均的若干块,每块的长度称为块长。因为通常情况下把区间分成块长为 \(\sqrt{n}\) 的块,所以分块算法的时间复杂度通常为 \(O(m\sqrt{n})\),具体时间复杂度根据所分的块长而定。
算法思想
假如把区间分成若干个相等的块,原本在区间上进行的操作就可以变成在若干个块上进行的操作。我们通常将一个长度为 \(n\) 的数组分成 \(\sqrt{n}\) 块块长为 \(\sqrt{n}\) 的块,如果 \(n\) 不是完全平方数,则我们还会分出一个多余的块。假设待操作的区间为 \([l, r]\),我们首先判断 \(l\) 和 \(r\) 是否在同一个块内。假如它们在同一块内,我们就直接对 \(l\) 和 \(r\) 所在的块进行操作。否则,我们将区间 \([l, r]\) 分成至少两块:最左边的一块,中间的若干个被完整覆盖的块,最右边的一块。
因为最左边的一块可能没有被完全覆盖,所以我们需要枚举左边的那一块被 \([l, r]\) 覆盖的部分,直接对每一个元素进行操作,最右边的一块同理。对于中间被完整覆盖的部分,我们可以直接对整块进行操作。我们利用线段树中的 \(lazy\) 思想,直接对整块进行加上某个数之类的操作。
分块不仅可以维护区间和,还可以维护区间中有多少个小于 \(k\) 的数、区间的最大公因数等等。分块的思想其实就是 优雅的暴力,只是特判了被整体覆盖的块。
参考代码
#include <cstdio>
#include <cmath>
using namespace std;
const int maxn = 5e5 + 5;
const int maxm = 2e3 + 5;
int n;
int a[maxn], st[maxm], ed[maxm];
int bel[maxn], lazy[maxm];
inline int read()
{
int res = 0, flag = 1;
char ch = getchar();
while (ch < '0' || ch > '9')
{
if (ch == '-')
flag = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9')
{
res = res * 10 + ch - '0';
ch = getchar();
}
return res * flag;
}
inline void update(int l, int r, int x)
{
if (bel[l] == bel[r])
for (register int i = l; i <= r; i++)
a[i] += x;
else
{
for (register int i = l; i <= ed[bel[l]]; i++)
a[i] += x;
for (register int i = st[bel[r]]; i <= r; i++)
a[i] += x;
for (register int i = bel[l] + 1; i < bel[r]; i++)
lazy[i] += x;
}
}
signed main()
{
int opt, l, r, c;
n = read();
int block = sqrt(n);
int size = ceil(n * 1.0 / block);
for (register int i = 1; i <= size; i++)
{
st[i] = (i - 1) * block + 1;
ed[i] = i * block;
}
ed[size] = n;
for (register int i = 1; i <= n; i++)
{
a[i] = read();
bel[i] = (i - 1) / block + 1;
}
for (register int i = 1; i <= n; i++)
{
opt = read();
l = read();
r = read();
c = read();
if (opt == 0)
update(l, r, c);
else
printf("%d\n", a[r] + lazy[bel[r]]);
}
return 0;
}