介绍下简单的分块:
当我们遇到区间类问题的时候,如何保证我们快速而高效地完成操作?
答案是线段树分块。
所谓分块,就是把一个序列分成许多块分别维护。是不是想起了树状数组 这样能大大提高效率:
例如,我们需要查询l,r中所有元素的和
利用分块,我们可以把1 2 3 4 5 6 7 8 9 10
分为
[1 2 3] [4 5 6] [7 8 9] [10]
比如,我想要3~10的元素和。这是,我们拿出3~10的区间:
...[3] [4 5 6] [7 8 9] [10]
而我们已经预处理了 [4 5 6]与 [7 8 9]的区间和,我们只需要算出两端的和就可以。这样,就可以大大提高查询的速度。
#include <cstdio> #include <cmath> #include <algorithm> using namespace std; const int MaxN = 100010, MaxB = 1010; int n, B, q, Cnt; int a[MaxN], Left[MaxB], Right[MaxB], Pos[MaxN]; long long sum[MaxN]; int main() { scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%d", &a[i]); // 分块预处理 B = max((int)sqrt(n), 1); for (int i = 1; i <= n; i++) { if (i % B == 1 % B) Cnt++; // 每块块首 Cnt++ Pos[i] = Cnt; // i 处在哪一块 if (Left[Cnt] == 0) Left[Cnt] = i; // 第Cnt块的左边 Right[Cnt] = i; // 第Cnt块的右边 } // for (int i = 1; i <= n; i++) printf("%d ", Pos[i]); puts(""); // for (int i = 1; i <= Cnt; i++) printf("%d %d\n", Left[i], Right[i]); for (int i = 1; i <= Cnt; i++) for (int j = Left[i]; j <= Right[i]; j++) sum[i] += a[j]; // sum[i] 表示的是第i块的和 // 处理询问 scanf("%d", &q); // a[l] + a[l + 1] + ... + a[r] 变成 sum[r] - sum[l - 1] // 这样我们可以只用求 l=1 的情况 // 但是我现在不这样做 for (int i = 1; i <= q; i++) { int l, r; scanf("%d %d", &l, &r); long long ans = 0; if (Pos[l] == Pos[r]) { // 1 l,r 在同一个块内 // [ l r ] for (int j = l; j <= r; j++) ans += a[j]; } else { // 2 l,r 不在同一个块内 // [ l ] [ ] [ ] [r ] for (int j = l; j <= Right[Pos[l]]; j++) ans += a[j]; for (int j = Right[Pos[l]] + 1; j <= Left[Pos[r]] - 1; j++) ans += sum[j]; for (int j = Left[Pos[r]]; j <= r; j++) ans += a[j]; } printf("%lld\n", ans); } return 0; }
(不是我写的,但是的确十分简洁易懂对吧)