前缀和算法精讲

一维前缀和:

适用题型:对于长度为 n 的序列,进行 m 次询问,每次查询区间 [L, R] 之间的数的和,朴素做法的复杂度为 n * m,而使用前缀和就将复杂度将为了 m 。

已知a[1], a[2], ..., a[i] 
则前缀和数组:
S[i] = a[1]+...+a[i]
s[1] = a[1], s[2] = s[1] + a[2], s[i] = s[i-1] + a[i] (规定s[0] = 0)

那么,对于每次询问,s[r] - s[l-1] 就是我们要求的区间和了:
s[r]   = a[1]+a[2]+...+a[l-1]+a[l]+a[l+1]+...+a[r];
s[l-1] = a[1]+a[2]+...+a[l-1];
通过一次运算求出任意一段区间内所有数之和,时间复杂度仅为O(1)
s[r] - s[l-1] = a[l]+a[l+1]+...+a[r];

题目:一维前缀和 题目来源:AcWing

  1. 预处理前缀和数组
  2. 用公式求区间和

训练:CSP2020-12-2:期末预测之最佳阈值

二维前缀和:

类似与一维前缀和,思维难度稍高一点。主要用在二维矩阵里,时间复杂度会由

s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j];

当要求某一矩形区域的和时,只需要知道在对角线上的两个顶点的 x, y 坐标

假设:左上(x1, y1),右下(x2, y2)

那么区域和计算方法为:

ans = s[x2][y2] - s[x2][y1-1] - s[x1-1][y2] + s[x1-1][y1-1]

题目:二维前缀和:子矩阵的和 题目来源:AcWing

题目描述:

输入一个n行m列的整数矩阵,再输入q个询问,每个询问包含四个整数x1, y1, x2, y2,表示一个子矩阵的左上角坐标和右下角坐标。
对于每个询问输出子矩阵中所有数的和。

输入格式

第一行包含三个整数n,m,q。 接下来n行,每行包含m个整数,表示整数矩阵。 接下来q行,每行包含四个整数x1, y1, x2,
y2,表示一组询问。

输出格式

共q行,每行输出一个询问的结果。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
typedef long long LL;
using namespace std;
const int MAXN = 1010;
int n, m, q, x1, y1, x2, y2;
LL a[MAXN][MAXN];
LL s[MAXN][MAXN];
int main() {
	scanf("%d %d %d", &n, &m, &q);
	for(int i = 1; i <= n; i++) {
		for(int j = 1; j <= m; j++) {
			scanf("%lld", &a[i][j]);
			s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j];
		}
	}
	while(q--) {
		scanf("%d %d %d %d", &x1, &y1, &x2, &y2);
		printf("%lld\n", s[x2][y2] - s[x2][y1-1] - s[x1-1][y2] + s[x1-1][y1-1]);
	}

	return 0;
}

训练:2021-04-2 邻域均值(二位前缀和的简单变形)

差分:

对于一个给定的数列 A,它的差分数列 B 定义为:
B[1] = A[1], B[i] = A[i] - A[i-1] (2 <= i <= n)
容易发现,“前缀和” 与 “差分” 是一对互逆运算,差分序列 B 的前缀和就是原序列 A,前缀和序列 S 的差分序列也是原序列 A。

给你一串长度为n的数列a1,a2,a3......an,要求对a[L]~a[R]进行m次操作:

操作一:将a[L]~a[R]内的元素都加上c

操作二:将a[L]~a[R]内的元素都减去c

最后再给出一个询问求a[L]-a[R]内的元素之和?

你会怎么做呢?你可能会想,我对于m次操作每次都遍历一遍a[L]~a[R],
给区间里的数都加上c或减去c,最后再求一次前缀和就行了,但这种朴素做法复杂度还是 m * n
对于1<=n,m<=1e5这个数据范围来说直接就GG了
是这个时候我们的差分就该派上用场了,我们新开一个数组b,储存每一次的修改操作
最后求前缀和的时候统计一下就能快速的得到正确答案了,

要对 l 到 r 内的数加 C 时:

b[l] += C, b[r+1] -= C ,就完成了操作

利用差分的概念,就没必要对区间内所有的数进行操作了。

例题: 差分 (模板题 hulu面试题)

题目描述:

输入一个长度为n的整数序列。 接下来输入m个操作,每个操作包含三个整数l, r, c,表示将序列中[l, r]之间的每个数加上c。
请你输出进行完所有操作后的序列。

输入格式

第一行包含两个整数n和m。 第二行包含n个整数,表示整数序列。 接下来m行,每行包含三个整数l,r,c,表示一个操作。

输出格式

共一行,包含n个整数,表示最终序列。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int n, m, l, r, c;
int a[100010], b[100010];
// 其实只需要一个数组就可以 
int main() {
	scanf("%d %d", &n, &m);
	for(int i = 1; i <= n; i++) {
		scanf("%d", &a[i]);
	}
	for(int i = 1; i <= n; i++) {
		b[i] = a[i] - a[i-1];
	}
	while(m--) {
		scanf("%d %d %d", &l, &r, &c);
		b[l] += c, b[r+1] -= c;
	}
	for(int i = 1; i <= n; i++) {
		printf("%d ", b[i] += b[i-1]);
		// 注意这里求新数组的方法 
	}
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值