一维前缀和,二维前缀和

一维前缀和

        所谓一维前缀和,听着高大上吗,实际上就是一个数列求和,用于快速,求出数组中字段的和。

一维前缀和原理:

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]减去蓝色部分的前缀和

 减去黄色部分前缀和

发现紫色部分被加了两次,所以需要再加上一次紫色部分

 最后得到红色部分的前缀和

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值