一.一维前缀和与差分.
什么是前缀和?
我们来考虑这样一个问题:
老方的导师派给老方一个任务:给定一个长度为 n
的序列, 要求完成以下两个操作:
- 输出前
k
项的和 - 输出区间
[L, r]
的和
我们考虑一下朴素算法。
对于操作1,可以在读入时一并处理,复杂度 O(k);
对于操作2,可以用一个循环遍历数组,复杂度 O(r - L);
然而如果数据范围很大的话,再考虑上读入的时间复杂度,是很不优的。
事实上,这个查询的复杂度是可以优化到 O(1) 的。
用前缀数组 pre[i]
来表示 序列前 i
项和,那么在读入时就可以完成对 pre[]
的初始化。
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
pre[i] = pre[i - 1] + a[i];
}
当我们查询的时候,可以直接用这个 pre[]
数组,也就是:
scanf("%d%d", &l, &r);
printf("%d%d", pre[r] - pre[l-1]);
这样时间复杂度就很优了。
什么是差分呢?
其实刚刚讲的查询就是差分。
我们可以这样理解,差分就是前缀和的目的,为了能有个好的复杂度,我们通过维护一个前缀和,再用它来实现差分。
实际上,一切具有可减性的操作都可以通过前缀和与差分来实现,然而区间最值这种不具有可减性的就不能用前缀和。
二.二维前缀和与差分
二维前缀和
老方在解决了上一个任务后,又受到另一位导师的委托:
给出一个 n × n
的矩阵,要求完成以下操作:
- 输出以
(1, 1)
为左下角,(a, b)
为右上角的矩阵中每个元素的和。 - 输出以
(a, b)
为左下角,(c, d)
为右上角的矩阵中每个元素的和。
在之前一维前缀和的基础上,我们想到,可以预处理一个二维数组 pre[]
,来优化时间复杂度。
如图所示:
红色矩阵的面积 = 黄色矩阵面积 + 绿色矩阵面积 - 蓝色矩阵面积 + 紫色矩阵面积。
因此我们可以这样初始化:
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
scanf("%d", a[i][j]);
pre[i][j] = pre[i-1][j] + pre[i][j-1] - pre[i-1][j-1] + a[i][j];
}
}
可以结合图片来理解。
差分
这里借用 lxl 上课时 ppt 里的一张图:
由图可得:
scanf("%d%d%d%d", &a, &b, &c, &d);
ans = pre[c][d] - pre[a][b] - pre[a-1][d] - pre[c][b-1] + pre[a-1][b-1];
printf("%d", ans);
三.例题
这道题几乎就是上面前缀和知识的直接体现。
看一眼数据范围,可以暴力枚举这个矩阵中的所有子矩阵,时间复杂度是 O(n^4)
,可以通过本题。