一维前缀和
所谓一维前缀和,听着高大上吗,实际上就是一个数列求和,用于快速,求出数组中字段的和。
一维前缀和原理:
s[1]=num[1];
s[2]=num[1]+num[2];
s[3]=num[1]+num[2]+num[3];
s[4]=num[1]+num[2]+num[3]+num[4];
...
也可看做:
a[l] + ... + a[r] = S[r] - S[l - 1]
...
也就是用S[i]存储num数组对应的前i个和。
进行初始化:
num[0]=0;
S[0]=0;
for(int i=1; i<=N; i++) {
scanf("%d",&num[i]);
S[i]=S[i-1]+num[i];
}
N为数字个数
下面举一个简单的例子:
字段段求和:
in:
给出一个长度为N的数组,进行Q次查询,查询从第i个元素开始长度为l的子段所有元素之和。
例如,1 3 7 9 -1,查询第2个元素开始长度为3的子段和,1 {3 7 9} -1。3 + 7 + 9 = 19,输出19。
out:
共Q行,对应Q次查询的计算结果。
Sample Input
5
1
3
7
9
-1
4
1 2
2 2
3 2
1 5
Sample Output
4
10
16
19
题目分析:
使用暴力必定会超时,所以使用前缀和:
代码:
#include <stdio.h>
int S[100010];
int num[10010];
int main() {
int N,M;
scanf("%d",&N);
num[0]=0;
S[0]=0;
for(int i=1; i<=N; i++) {
scanf("%d",&num[i]);
S[i]=S[i-1]+num[i];
}
scanf("%d",&M);
while(M--!=0) {
int a,b;
scanf("%d %d",&a,&b);
printf("%d",S[a+b-1]-S[a-1]);
}
}
暴力的代码:
#include <stdio.h>
int num[10010];
int S[10010];
int main() {
int N;
scanf("%d",&N);
S[0]=0;
num[0]=0;
for(int i=1; i<=N; i++) {
scanf("%d",&num[i]);
S[i]=S[i-1]+num[i];
}
int count;
scanf("%d",&count);
while(count--!=0) {
int a,b;
int ans=0;
scanf("%d %d",&a,&b);
for(int i=a; i<=a+b-1; i++) {
ans+=num[i];
}
printf("%d",ans);
}
return 0;
}
总结:
从这个题就可以看出,前缀和的优点:
对于特定问题时间复杂度上非常友好
注意:
1.为了方便使用,前缀和数组下标从1开始,不方便在哪里?
为了节省录入数据的时间,我们将数据的录入和数组的初始化放在一个循环里面,S[1]=S[0]+num[1];,将S[0]初始化为零,
二维前缀和:
一维前缀和是基于线性数组,而二位前缀和,是基于矩阵,计算过程中使用了容斥定理:
抱歉,后期送上动图
可以推导出原理: sum[i,j]=sum[i,j-1]+sum[i-1,j]-sum[i-1,j-1]+a[i,j];
因为:将黄色区域加了两次,所以要再减去一次,每一个前缀和都是建立在前面的基础上,层层相关,直到第一个,所以有一点和一维一样,第一行和第一列都是零 :
二维前缀和的初始化:
int n;//行
int m;//列
sum[0][0]=0;
scanf("%d %d",&n,&m);
for (int i=1; i<=n; i++) {
for (int j=1; j<=m; j++) {
scanf("%d",&num[i][j]);
sum[i][j] = sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+num[i][j];
}
}
和一维前缀和几乎相同:
in:
第一行包含三个整数n,m,q。接下来n行,每行包含m个整数,表示整数矩阵。接下来q行,每行包含四个整数x1, y1, x2, y2,表示一组询问。
out:
输出对应矩阵和
输入样例:
3 4 3
1 7 2 4
3 6 2 8
2 1 2 3
1 1 2 2
2 1 3 4
1 3 3 4
输出样例:
17
27
21
题目分析:
使用二维前缀和
代码:
#include <stdio.h>
int num[10010][10010];
int sum[10010][10010];
int main() {
int n;//行
int m;//列
sum[0][0]=0;
scanf("%d %d",&n,&m);
int count;
//询问次数:
scanf("%d",&count);
for (int i=1; i<=n; i++) {
for (int j=1; j<=m; j++) {
scanf("%d",&num[i][j]);
sum[i][j] = sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+num[i][j];
}
}
while(count--!=0) {
int x2,y2;
int x1,y1;
scanf("%d %d %d %d",&x1,&y1,&x2,&y2);
printf("%d",sum[x2][y2]-sum[x2][y1-1]-sum[x1-1][y2]+sum[x1-1][y1-1]);
}
return 0;
}
子矩阵和输出:
求红色部分的前缀和
用sum[i][j]减去蓝色部分的前缀和
减去黄色部分前缀和
发现紫色部分被加了两次,所以需要再加上一次紫色部分
最后得到红色部分的前缀和