引语
来学前缀和!
PS:此文中含有一个角色小A,我将会努力插入这个名为"小A"的角色来丰富文章趣味
正文
前缀和的定义
小A:前缀和?是什么?好吃吗,阿巴阿巴
前缀和,就是指某个序列的前n项的和
而前缀和算法,就是指通过求出某个序列的前n项的和来进行快速地区间和计算
前缀和还可以用来求一个二维矩阵的元素和
——本文章
前缀和是指某序列的前n项和,可以把它理解为数学上的数列的前n项和,而差分可以看成前缀和的逆运算。合理的使用前缀和与差分,可以将某些复杂的问题简单化。
——CSDN某文章1
假定给我们一个数组,前缀和就是该元素前所以元素和。也就是如果我们设定一个数组s为前缀和数组,那么s[3]就是我们原数组前三个元素之和,这就是我们的前缀和。
——CSDN某文章2
前缀和是指某序列的前n项和,可以把它理解为数学上的数列的前n项和。合理的使用前缀和预处理,能大大降低时间复杂度,
构造前缀和数组需要O(n)时间复杂度和空间复杂度,查询操作只需要常数时间。
——CSDN某文章3
前缀和是一个数组的某项下标之前(包括此项元素)的所有数组元素的和。分为一维和二维,一维前缀和可以很快速的求序列中某一段的和。而二维前缀和可以快速求一个矩阵中某个子矩阵的和。
作用:快速求出元素组中某段区间的和
定义:前缀和是指某一序列的前 n 项和。
——CSDN某文章5
前缀和可以简单理解为「数列的前n项的和」,是一种重要的预处理方式,能大大降低查询的时间复杂度。
小A:好,然后呢?
一维前缀和
例题引入
思路1
小A:啊多简单啊,直接暴力!暴力YYDS!
旁白:"代码大牛"小A立马写好了一份代码
#include <bits/stdc++.h>
using namespace std;
long long n,m,a[100005];
int main(){
cin >> n >> m;
for (int i = 1;i <= n;i++) cin >> a[i];
while (m--){
long long l,r,sum = 0;
cin >> l >> r;
for (int i = l;i <= r;i++) sum += a[i];
cout << sum << endl;
}
return 0;
}
旁白:果不其然,小A成功TLE
小A:啊?为什么
你仔细看看数据范围
小A:1≤n,m≤.....100000?
旁白:恭喜玩家[小A]获得成就[不看数据范围的后果]
小A:那应该怎么做呢...
思路2
预处理!从第一个数字到某个数字为止,这样的区间总共有n 种(设n为元素总数)从某个数字到n为止,这样的区间总共有n 种(设n为元素总数)
小A:所以我们要预处理什么?
当然是预处理这些区间的和啦!
观察下图
那么我们还可写成什么形式?
此处我们设l为2,r为4
所以说,区间[l,r]的和=数字总和-[1,l-1]的和-[r+1,n]的和
小A:哦!你的意思是让我们计算前面的和与后面的和?
其实也没有那么复杂啦
我们在以上改进,不计算后面的和,仅计算前面的和,我们使用数组sum[n]来预处理前n个数的和
小A:我知道了!sum[n]=a[1]+a[2]+...+a[n]!
正确!那么请你思考,如何根据sum数组求区间和呢?
小A:额...我知道了!
旁白:小A的图片:
小A:sum[r]就是sum[l-1]+区间[l,r],所以,区间[l,r]=sum[r]-sum[l-1]
旁白:小A立马开始着手写代码
小A:AC了!
总结模板
#include <bits/stdc++.h>
using namespace std;
long long n,m,a[100005],sum[100005];
int main(){
cin >> n >> m;
for (int i = 1;i <= n;i++) cin >> a[i],sum[i] = sum[i - 1] + a[i];
while (m--){
long long l,r;
cin >> l >> r;
cout << sum[r] - sum[l - 1] << endl;
}
return 0;
}
二维前缀和
例题引入
思路1
小A:这次我可不会被坑了
旁白:剧情需要,小A
被迫又在写暴力代码
#include <bits/stdc++.h>
using namespace std;
long long n,m,q,a[1005][1005];
int main(){
cin >> n >> m >> q;
for (int i = 1;i <= n;i++){
for (int j = 1;j <= m;j++) cin >> a[i][j];
}
while (q--){
long long x1,y1,x2,y2,sum = 0;
cin >> x1 >> y1 >> x2 >> y2;
for (int i = x1;i <= x2;i++){
for (int j = y1;j <= y2;j++) sum += a[i][j];
}
cout << sum << endl;
}
return 0;
}
旁白:果不其然,小A又成功TLE
旁白:恭喜玩家[小A]获得成就[被迫暴力TLE]
思路2
小A:我知道!肯定是预处理!
怎么预处理?
小A:额
上图片
我们可以将sum[i][j]表示为以i,j为右下角的矩阵元素和
我们发现,矩阵和是:S = 橙框区域+黄框区域-红框区域+绿框区域(有重叠) ,替换成sum值和矩阵a中的元素就是:
sum[i][j] = sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1] + a[i][j]
小A:预处理我写好啦!
for (int i = 1;i <= n;i++){
for (int j = 1;j <= m;j++){
cin >> a[i][j];
sum[i][j] = sum[i-1][j] + sum[i][j-1] - sum[i-1][j-1] + a[i][j];
}
}
那么怎么用sum数组呢?
小A:容
朕我思索一下我知道了!
小A:D = S(总和) - B - C + A
小A:如果想要求出以(a,b)为左上角,(c,d)为右下角的子矩阵和
小A:就用:sum[c][d] - sum[a-1][b] - sum[a][b-1] + sum[a-1][b-1]
旁白:小A写出了代码
总结模板
#include <bits/stdc++.h>
using namespace std;
int n,m,q,a[1005][1005],sum[1005][1005];
int main(){
cin >> n >> m >> q;
for (int i = 1;i <= n;i++){
for (int j = 1;j <= m;j++){
cin >> a[i][j];
sum[i][j] = sum[i-1][j] + sum[i][j-1] - sum[i-1][j-1] + a[i][j];
}
}
while (q--){
int x1,y1,x2,y2;
cin >> x1 >> y1 >> x2 >> y2;
cout << sum[x2][y2] - sum[x1 - 1][y2] - sum[x2][y1 - 1] + sum[x1 - 1][y1 - 1] << endl;
}
return 0;
}
结语
多多练习!