【基础算法模板】前缀和与差分

目录

1、一维前缀和【O( 1 )】

作用:

公式:

原理:

模板:

测试:

2、二维前缀和 

作用:

公式:

原理:

模板:

测试:

3、一维差分

作用:

公式:

原理:

模板:

测试:

4、二维差分

作用:

公式:

原理:

模板:

测试:


1、一维前缀和【O( 1 )】

作用:

  • 快速求出序列中某一区间【l,r】的和

公式:

  • a[l] + ... + a[r] = S[r] - S[l - 1]

原理:

  • S[ l - 1 ] = a[ 1 ] + a[ 2 ] + ... a[ l - 1 ]                                  ①
  • S[ r ] =      a[ 1 ] + a[ 2 ] + ... a[ l - 1 ] + a[ l ] + ... a[ r ]        ②
  • ② - ① 得 :S[ r ] - S[ l - 1 ] = a[ l ] + a[ l + 1 ] + a[ l + 2 ] + ... a[ r ]

模板:

i >= 1
S[i] = 0
S[i] = a[1] + a[2] + ... a[i]
a[l] + ... + a[r] = S[r] - S[l - 1]

测试:

//输入:长度为 n 的序列,提供 m 行数据,每行数据两个值 l  r, l为区间左边界, r为区间右边界
//输出:【l,r】区间的长度和 
#include <iostream>
using namespace std;

const int N = 100010;

int n,m;
int a[N],s[N];

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++)	s[i] = s[i-1]+a[i];
	
	while(m--){
		int l,r;
		scanf("%d%d",&l,&r);
		printf("%d\n",s[r]-s[l-1]);
	}
	
	return 0;
}

/*
5 3
2 1 3 6 4
1 2
1 3
2 4

3
6
10
*/

2、二维前缀和 

作用:

  • 快速求矩形区间中所有数的和

公式:

  • 矩形区域【(x1, y1), (x2, y2)】求和:
    • S[x2, y2] - S[x1 - 1, y2] - S[x2, y1 - 1] + S[x1 - 1, y1 - 1]       ①  
  • 如何去算 S[x, y] 面积:
    • S[x, y] = S[x - 1, y] + S[x, y - 1] - S[x - 1, y - 1] + a[x, y]        ②

原理

  • 如图所示,矩形【(x1, y1), (x2, y2)】的数求和(简称图中绿色面积)= 矩形【(0, 0), (x2, y2)】的面积 - L型面积(图中红色与紫色取并集的面积),L型面积 = 红色面积 + 紫色面积 - 红色与紫色取交集的面积。整理得:绿色面积 = 矩形【(0, 0), (x2, y2)】的面积 - 红色面积 - 紫色面积 + 红色与紫色取交集的面积,①式证毕。
  • 与①式方法一致,如果令 x1 = x2 , y1 = y2 , 代入①式, 可以得到:矩形【(x2, y2), (x2, y2)】的面积 = S[x2, y2] - S[x2 - 1, y2] - S[x2, y2 - 1] + S[x2 - 1, y2 - 1],用 x 替换 x1, y 替换 y1, 得到:矩形【(x, y), (x, y)】的面积 = S[x, y] - S[x - 1, y] - S[x, y - 1] + S[x - 1, y - 1],矩形【(x, y), (x, y)】的面积就是 a[x, y] 的数值,代入式子:a[x, y] = S[x, y] - S[x - 1, y] - S[x, y - 1] + S[x - 1, y - 1],移向推出②式,②式证毕。

模板

S[i, j] = 第i行j列格子左上部分所有元素的和
以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵的和为:
S[x2, y2] - S[x1 - 1, y2] - S[x2, y1 - 1] + S[x1 - 1, y1 - 1]

测试:

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

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


输出格式:
共q行,每行输出一个询问的结果 


数据范围
1<=n,m<=1000,
1<=q<=100000,
1<=x1<=x2<=n,
1<=y1<=y2<=m,
-1000<=矩阵内元素的值<=1000

输入样例:
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 <iostream>
using namespace std;

const int N = 1010;

int n, m , q;

int a[N][N], s[N][N]; 

int main(){
	
	scanf("%d%d%d", &n, &m, &q);
	for(int i=1; i<=n; i++)
		for(int j=1; j<=m; j++)
			scanf("%d",  &a[i][j]);
	for(int i=1; i<=n; i++)
		for(int j=1; j<=m; j++)
			s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];				//求前缀和 
	
	while(q--){
		int x1, y1, x2, y2;
		scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
		printf("%d\n", s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1]);	//算子矩阵和 
	}
	
	return 0;
}








3、一维差分

作用:

  • O(1)的时间给原数组的某个连续区间中的每个元素加上或减去一个固定的值。

公式:

  • 给定数组: a[ 1 ],a[ 2 ] + ...a[ l ],a[ l + 1 ] + ...  a[ r ] + a[ r + 1 ] ... a[ n ]
  • 定义差分数组 b[ i ] = a[ i ] - a[ i - 1 ],b[ 1 ] = a[ 1 ],这样a数组就变成了b数组的前缀和
  • 构造差分:b[ 1 ],b[ 2 ] + ...b[ l ],b[ l + 1 ] + ...  b[ r ] + b[ r + 1 ] ... b[ n ]
  • 区间[ l, r ] + c:b[ l ] += c,b[ r + 1 ] -=c
  • 根据 a[ i ] = a[ i - 1] + b[ i ],a[ 1 ] = b[ 1 ], 还原数组,得到结果

原理:

  • 差分是前缀和的逆运算,举个例子(数组起始下标为 0 ):给定数组a[ ]:[ 1, 3, 43, 5, 7, 9],想要对子区间[ 3, 43, 5 ] 上的每个数 +7,我们构造差分数组 b[ ]:[ 1, 2, 40, -38, 2, 2],先对 b[ 1 ] += 7,得到 b[ ]:[ 1, 2 + 7, 40, -38, 2, 2] ,这时通过 a[ i ] = a[ i - 1] + b[ i ] 去还原 a[ ] 数组,得到 a[ ]:[ 1, 10, , 50, 12, 14, 16 ],发现从下标 1 及之后的位置的元素都 +7,这是因为还原的过程是一个递推的过程,对某一个位置操作的结果会继承到下一次递推中。然后对 b[ 3 + 1 ] -= 7,同理会使区间 b[ 4 ] , ... , b[ n ] 的元素都 -7b[ 4 ] , ... , b[ n ] 的元素 +7-7 等价于没变。这样就实现了仅对 b[ 1 ] , ... , b[ 3 ] 上的每个数 +7
  • 值得注意的是,如果有 m(m>1)对区间的加减操作,就仅需要在这些操作都结束时再还原数组,不用每操作一次就还原一次,因为还原数组的过程需要遍历,时间复杂度O(n),每操作一次就还原一次,就经过了 m 次还原,总复杂度就为 O(m*n)。这样做更好:开始时构建差分数组,O(n)m (m>1)对区间的加减操作,O(1);最后还原数组,O(n),总时间为O(n)
  • 此外,在构造差分数组 b[ ] 时,不需要借助公式:b[ i ] = a[ i ] - a[ i - 1 ],b[ 1 ] = a[ 1 ] 构建。
    • 我们先假定 a[ ] 中每个元素均为 0 ,则 b[ ] 也均为 0
    • 下标 i 位置实际上值为 a[ i ],可以看成插入操作:给元素均为 0 的区间[ i, i ]上的数加上 a[ i ],这样就转换成了模板公式。
    • 套用模板公式:
      • 下标为 i 时,b[ i ] += a[ i ],b[ i + 1] -= a[ i ];                             ①
      • 下标为 i + 1 时,b[ i + 1 ] += a[ i + 1 ],b[ i + 2] -= a[ i + 1 ];     ②
    • 观察式①、式②。 当 i == 1时,b[ 1 ] += a[ 1 ] 这就等价于 b[ 1 ] = a[ 1 ];当 i == n 时,b[ n ] += a[ n ]b[ n+1 ] -= a[ n ];(n+1不在范围内相当于没有这句,但注意越界问题),i != 1 && i != n,在式①中 b[ i + 1] -= a[ i ],在式②中 b[ i + 1 ] += a[ i + 1 ],所以 b[ i + 1] = a[ i + 1] - a[ i ],这与 b[ i ] = a[ i ] - a[ i - 1 ] 是一个操作。
    • 在测试代码中以这种方式构建差分数组。

模板:

给区间[l, r]中的每个数加上c:B[l] += c, B[r + 1] -= c

void insert(int l, int r, int c){
	b[l] += c;
	b[r + 1] -= c;
} 

测试:

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

1<=n,m<=100000
1<=l<=r<=n
-1000<=c<=1000
-1000<=整数序列中元素的值<=1000 


输入样例:
6 3
1 2 2 1 2 1
1 3 1
3 5 1
1 6 1

输出样例:
3 4 5 3 4 2

*/ 
#include <iostream>
using namespace std;


const int N = 100010;

int n, m;
int a[N], b[N];

void insert(int l, int r, int c){
	b[l] += c;
	b[r + 1] -= c;
} 



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 ++)	insert(i, i, a[i]);	//构建差分数组 
	
	while(m -- ){
		int l, r, c;
		scanf("%d%d%d",&l, &r, &c);
		insert(l, r, c);
	}
	//a[]是b[]的前缀和,求b[]的前缀和得到更新后的数组,存入b[]中 
	for(int i = 1; i <= n; i ++)	b[i] += b[i - 1];
	for(int i = 1; i <= n; i ++)	printf("%d", b[i]); 
	
	
	return 0;
}


4、二维差分

作用:

  • O(1)的时间给原二位矩阵的某个子矩阵中每个元素加上或减去一个固定的值。

公式:

  • 给定 a[ n ][ m ] 数组,假定模型 b[ m ][ n ] 使得 a[ m ][ n ] 存 b[ m ][ n ] 的前缀和,则 b[ m ][ n ] 就为 a[ m ][ n ]的逆运算
  • 矩形区域【(x1, y1), (x2, y2)】+ c
    • b[x, y] += c,相当于将 区域【(x, y), (m, n)】+ c
    • b[x1, y1] += c, b[x2 + 1, y1] -= c, b[x1, y2 + 1] -= c, b[x2 + 1, y2 + 1] += c

原理:

  • 如图所示,矩形【(x1, y1), (x2, y2)】的数 + c = (矩形【(x1, y1), (m, n)】的数 + c )&& (红色区域 - c,把多加的 c 消去)&& (紫色区域 - c,把多加的 c 消去)&& (红色与紫色交集区域 + c,这个地方多减了一次)。
  • 构建并初始化差分矩阵:
    • 同一维差分,假定原矩阵 a[ ][ ] 均为 0 ,则 b[ ][ ] 也均为 0。我们可以用二位差分给二维表上的任意矩阵区间加上任意值,那么 向(i,j) 插入实际存的值 就转换为 对矩阵【(i, j), (i, j)】利用二维差分模板 加上 a[ i ][ j ]实际的值。在这个过程中a[ ][ ] 不是真的为 0,为 0 是我们假定的,但是b[ ][ ] 是真的为 0就是说我们利用这个操作不是真的去插入a[ ][ ] 的实际值,而是模拟插入a[ ][ ] 的实际值的过程去构建并初始化差分矩阵b[ ][ ]

模板:

给以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵中的所有元素加上c:
B[x1, y1] += c, B[x2 + 1, y1] -= c, B[x1, y2 + 1] -= c, B[x2 + 1, y2 + 1] += c

void insert(int x1, int y1, int x2, int y2, int c){
	b[x1][y1] += c;
	b[x2 + 1][y1] -= c; 
	b[x1][y2 + 1] -= c;
	b[x2 + 1][y2 + 1] +=c ;
}

测试:

/*
输入一个n行m列的整数矩阵,再输入q个操作,每个操作包含五个整数x1,y1,x2,y2,c,
其中(x1,y1)和 (x2,y2)表示一个矩阵的左上角坐标和右下角坐标。

每个操作都要将选中的每个元素的值加上c
请输出进行完所有操作后的矩阵。 

1<=n,m<=1000
1<=q<=100000
1<=x1<=x2<=n
1<=y1<=y2<=m
-1000<=c<=1000
-1000<=矩阵内元素的值<=1000 


输入样例:
3 4 3
1 2 2 1
3 2 2 1
1 1 1 1
1 1 2 2 1
1 3 2 3 2
3 1 3 4 1

输出样例:
2 3 4 1
4 3 4 1
2 2 2 2

*/ 
#include <iostream>
using namespace std;


const int N = 1010;

int n, m, q;
int a[N][N], b[N][N], a_new[N][N];

void insert(int x1, int y1, int x2, int y2, int c){
	b[x1][y1] += c;
	b[x2 + 1][y1] -= c; 
	b[x1][y2 + 1] -= c;
	b[x2 + 1][y2 + 1] +=c ;
}

int main(){
	scanf("%d%d%d", &n, &m, &q);
	
	//初始化a[][] 
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= m; j++)
			scanf("%d",&a[i][j]);
	
	//假定a[][]均为0,模拟插入a[ ][ ]的实际值的过程去初始化差分矩阵b[ ][ ]		
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= m; j++)
			insert(i, j, i, j, a[i][j]);
	
	//根据要求改变差分数组 
	while(q --){
		int x1, y1, x2, y2, c;
		cin >> x1 >> y1 >> x2 >> y2 >> c;
		insert(x1, y1, x2, y2, c); 
	}
	
	//因为a[][]是b[][]的前缀和,利用前缀和公式还原, 存到a_new[][]中,即为改变后的矩阵 
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= m; j++)
			a_new[i][j] = a_new[i - 1][j] + a_new[i][j - 1] - a_new[i - 1][j - 1] + b[i][j];
			
			
	for(int i = 1; i <= n; i++){
		for(int j = 1; j <= m; j++)	printf("%d ", a_new[i][j]);
		puts("");
	}
		
	
	return 0;
}




【注】:各位帅哥美女,休息一下吧,记得点赞哦! (⁎⁍̴̛ᴗ⁍̴̛⁎)

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值