Acwing学习笔记二

一.前缀和

1.前缀和是什么

前缀和是指某序列的前 n n n项和,可以把它理解为数学上的数列的前 n n n项和,即 S n = a 1 + a 2 + ⋯ + a n S_n=a_1+a_2+\cdots+a_n Sn=a1+a2++an

2.前缀和有什么用

前缀和是一种预处理,用于降低查询时的时间复杂度。 举个例子:给定 n n n个整数,然后进行 m m m次询问,每次询问求一个区间内值的和。
如果用暴力写法,那每次询问都需要从区间左端点循环到区间右端点求和,时间复杂度较大。 这种时候就可以预先求出该数组的一维前缀和。
a n s = s [ r ] − s [ l − 1 ] ans=s[r]-s[l-1] ans=s[r]s[l1],其中 l l l r r r是给定的区间。每次询问可直接输出答案,这样时间复杂度就降到了 O ( N + M ) O(N+M) O(N+M)

3.前缀和怎么用

1.一维前缀和

1.定义

对于一维数组的前缀和计算我们称之为一维前缀和

2.原理

S [ 0 ] = 0 S[0]=0 S[0]=0
S [ r ] = a [ 1 ] + a [ 2 ] + ⋯ + a [ l − 1 ] + a [ l ] + a [ l + 1 ] + ⋯ + a [ r ] S[r] =a[1]+a[2]+\cdots+a[l-1]+a[l]+a[l+1]+\cdots+a[r] S[r]=a[1]+a[2]++a[l1]+a[l]+a[l+1]++a[r]
S [ l − 1 ] = a [ 1 ] + a [ 2 ] + ⋯ + a [ l − 1 ] S[l-1]=a[1]+a[2]+\cdots+a[l-1] S[l1]=a[1]+a[2]++a[l1]
S [ r ] − S [ l − 1 ] = a [ l ] + a [ l + 1 ] + ⋯ + a [ r ] S[r]-S[l-1]=a[l]+a[l+1]+\cdots+a[r] S[r]S[l1]=a[l]+a[l+1]++a[r]

3.图解


如上图所示, S [ r ] S[r] S[r] S [ l − 1 ] S[l-1] S[l1]有许多重复元素,因此可以进行相减来求取任一段落的和值。

4.代码实现
const int N=100010;
int S[N],a[N]; //a[N]是一维数组,S[N]是前缀和
for(int i=1;i<=n;i++){ 
    S[i]=S[i-1]+a[i];   
}

2.二维前缀和

1.定义

对于二维数组的前缀和计算我们称之为二维前缀和

2.原理

S [ 0 ] [ 0 ] = 0 S[0][0]=0 S[0][0]=0
S [ i ] [ j ] S[i][j] S[i][j] = 第 i i i j j j列格子左上部分所有元素的和为:
S [ i ] [ j ] = S [ i − 1 ] [ j ] + S [ i ] [ j − 1 ] − S [ i − 1 ] [ j − 1 ] + a [ i ] [ j ] S[i] [j] = S[i-1][j] + S[i][j-1 ] - S[i-1][ j-1] + a[i][j] S[i][j]=S[i1][j]+S[i][j1]S[i1][j1]+a[i][j]
( x 1 , y 1 ) (x_1, y_1) (x1,y1)为左上角, ( x 2 , y 2 ) (x_2, y_2) (x2,y2)为右下角的子矩阵的和为:
S [ x 2 ] [ y 2 ] − S [ x 1 − 1 ] [ y 2 ] − S [ x 2 ] [ y 1 − 1 ] + S [ x 1 − 1 ] [ y 1 − 1 ] S[x_2][y_2] - S[x_1 - 1][y_2] - S[x_2][y_1 - 1] + S[x_1 - 1][y_1 - 1] S[x2][y2]S[x11][y2]S[x2][y11]+S[x11][y11]

3.图解


上图是二维数组前缀和的图像表示。
下图是对 S [ i ] [ j ] S[i][j] S[i][j]的推导:

紫色面积是指 ( 1 , 1 ) (1,1) (1,1)左上角到 ( i , j − 1 ) (i,j-1) (i,j1)右下角的矩形面积,绿色面积是指
( 1 , 1 ) (1,1) (1,1)左上角到 ( i − 1 , j ) (i-1, j) (i1,j)右下角的矩形面积,红色面积是指 ( 1 , 1 ) (1,1) (1,1)
左上角到 ( i − 1 , j − 1 ) (i-1,j-1) (i1,j1)右下角的矩形面积,蓝色面积是指 ( i − 1 , j − 1 ) (i-1,j-1) (i1,j1)左上角到 ( i , j ) (i,j) (i,j)右下角的矩形面积。

从图中我们很容易看出,整个外围蓝色矩形面积 S [ i ] [ j ] S[i][j] S[i][j] = 绿色面积 S [ i − 1 ] [ j ] S[i-1][j] S[i1][j] + 紫色面积 S [ i ] [ j − 1 ] S[i][j-1] S[i][j1] - 红色面积 S [ i − 1 ] [ j − 1 ] S[i-1][j-1] S[i1][j1] + 小蓝色面积 a [ i ] [ j ] a[i][j] a[i][j]
下图是对子矩阵的和的推导:

紫色面积是指 ( 1 , 1 ) (1,1) (1,1)左上角到 ( x 1 − 1 , y 2 ) (x_1-1,y_2) (x11,y2)右下角的矩形面积 ,棕色面积是指 ( 1 , 1 ) (1,1) (1,1)左上角到 ( x 2 , y 1 − 1 ) (x_2,y_1-1) (x2,y11)右下角的矩形面积,红色面积是指 ( 1 , 1 ) (1,1) (1,1)左上角到 ( x 1 − 1 , y 1 − 1 ) (x_1-1,y_1-1) (x11,y11)右下角的矩形面积,绿色面积是指 ( x 1 , y 1 ) (x_1,y_1) (x1,y1)左上角到 ( x 2 , y 2 ) (x_2,y_2) (x2,y2)右下角的矩形面积。

绿色矩形的面积 = 整个蓝色矩形面积 S [ x 2 ] [ y 2 ] S[x_2][y_2] S[x2][y2]- 棕色面积 S [ x 2 ] [ y 1 − 1 ] S[x_2][y_1-1] S[x2][y11] - 紫色面积 S [ x 1 − 1 ] [ y 2 ] S[x_1- 1][y_2] S[x11][y2] + 红色面积 S [ x 1 − 1 ] [ y 1 − 1 ] S[x_1-1][y_1-1] S[x11][y11]

4.代码实现
const int N=100010;
int a[N][N],S[N][N];//a[N][N]是二维数组,S[N][N]是二维前缀和
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];
      }
}

二.差分

1.差分是什么

类似于高数中积分与微分的关系,差分也是前缀和的逆运算。

2.差分有什么用

差分类似于前缀和,也是一种预处理,用于降低查询时的时间复杂度。 举个例子:给定 n n n个整数,然后进行 m m m次询问,每次询问对一个区间进行加 c c c操作。
如果用暴力写法,那每次询问都需要从区间左端点循环到区间右端点进行操作,时间复杂度较大。 这种时候就可以预先求出该数组的一维差分。
b [ l [ + = c , b [ r + 1 ] − = c b[l[ += c,b[r+1] -= c b[l[+=cb[r+1]=c,其中 l l l r r r是给定的区间。每次询问只需要对差分数组b[n]中的两个数进行操作即可,这样时间复杂度就降到了 O ( 1 ) O(1) O(1)

3.差分怎么用

差分中最重要的知识点就是差分数组。
差分数组:
首先给定一个原数组 a a a a [ 1 ] , a [ 2 ] , a [ 3 ] ⋯   , a [ n ] a[1], a[2], a[3]\cdots, a[n] a[1],a[2],a[3],a[n];
然后我们构造一个数组 b b b b [ 1 ] , b [ 2 ] , b [ 3 ] ⋯   , b [ i ] b[1] ,b[2] , b[3]\cdots, b[i] b[1],b[2],b[3],b[i];
使得 a [ i ] = b [ 1 ] + b [ 2 ] + b [ 3 ] + ⋯ + b [ i ] a[i] = b[1] + b[2 ]+ b[3] +\cdots+ b[i] a[i]=b[1]+b[2]+b[3]++b[i]
也就是说, a a a数组是 b b b数组的前缀和数组,反过来我们把 b b b数组叫做 a a a数组的差分数组。也就是说,每一个 a [ i ] a[i] a[i]都是 b b b数组中从头开始的一段区间和。

1.一维差分

1.差分数组的构造

差分数组 b b b的构造: b [ i ] = a [ i ] − a [ i − 1 ] b[i]=a[i]-a[i-1] b[i]=a[i]a[i1]

2.差分数组的实际运用

给定一个区间 [ l , r ] [l ,r ] [l,r],让我们把 a a a数组中的 [ l , r ] [ l, r] [l,r]区间中的每一个数都加上 c c c
始终要记得, a a a数组是 b b b数组的前缀和数组,因此对 b b b数组的 b [ i ] b[i] b[i]的修改,会影响到 a a a数组中从 a [ i ] a[i] a[i]及往后的每一个数。
如果采取暴力做法,我们需要 f o r for for循环 l l l r r r区间,时间复杂度为 O ( n ) O(n) O(n),如果我们需要对原数组执行 m m m次这样的操作,时间复杂度就会变成 O ( n ∗ m ) O(n*m) O(nm)
而如果考虑差分做法,我们只需要进行两步操作:
首先让差分数组 b b b中的 b [ l ] + c b[l] + c b[l]+c a a a数组从 a [ l ] a[l] a[l]开始均加上了 c c c
然后, b [ r + 1 ] − c b[r+1] - c b[r+1]c a a a数组从 a [ r + 1 ] a[r+1] a[r+1]开始均减上了 c c c
时间复杂度大大减少。

b [ l ] + c b[l] + c b[l]+c的效果使得 a a a数组中 a [ l ] a[l] a[l]及以后的数都加上了 c c c(红色部分),但我们只要求 l l l r r r区间加上 c c c, 因此还需要执行 b [ r + 1 ] − c b[r+1] - c b[r+1]c,让 a a a数组中 a [ r + 1 ] a[r+1] a[r+1]及往后的区间再减去 c c c(绿色部分),这样对于 a [ r ] a[r] a[r]以后区间的数相当于没有发生改变。

2.二维差分

1.差分数组的运用

而差分数组 b b b的构造: b [ i ] [ j ] = a [ i ] [ j ] − a [ i − 1 ] [ j ] − a [ i ] [ j − 1 ] + a [ i − 1 ] [ j − 1 ] b[i][j]=a[i][j]−a[i−1][j]−a[i][j−1]+a[i−1][j−1] b[i][j]=a[i][j]a[i1][j]a[i][j1]+a[i1][j1]

2.差分数组的实际运用

如果 a [ ] [ ] a[][] a[][]数组是 b [ ] [ ] b[][] b[][]数组的前缀和数组,那么 b [ ] [ ] b[][] b[][]就是 a [ ] [ ] a[][] a[][]的差分数组
始终要记得, a a a数组是 b b b数组的前缀和数组,因此对 b b b数组的 b [ i ] [ j ] b[i][j] b[i][j]的修改,会影响到 a a a数组中从 a [ i ] [ j ] a[i][j] a[i][j]及往后的每一个数。
假定我们已经构造好了 b b b数组,类比一维差分,我们执行以下操作
来使被选中的子矩阵中的每个元素的值加上 c c c
b [ x 1 ] [ y 1 ] + = c b[x_1][y_1] += c b[x1][y1]+=c
b [ x 1 ] [ y 2 + 1 ] − = c b[x_1][y_2+1] -= c b[x1][y2+1]=c
b [ x 2 + 1 ] [ y 1 ] − = c b[x_2+1][y_1] -= c b[x2+1][y1]=c
b [ x 2 + 1 ] [ y 2 + 1 ] + = c b[x_2+1][y_2+1] += c b[x2+1][y2+1]+=c
实际效果如下图:

b [ x 1 ] [ y 1 ] + = c b[x_1][y_1] +=c b[x1][y1]+=c是让整个 a a a数组中蓝色矩形面积的元素都加上了 c c c
b [ x 1 ] [ y 2 + 1 ] − = c b[x_1][y_2+1]-=c b[x1][y2+1]=c是让整个 a a a数组中绿色矩形面积的元素再减去 c c c,使其内元素不发生改变
b [ x 2 + 1 ] [ y 1 ] − = c b[x_2+1][y_1]- =c b[x2+1][y1]=c是让整个 a a a数组中紫色矩形面积的元素再减去 c c c,使其内元素不发生改变
b [ x 2 + 1 ] [ y 2 + 1 ] + = c b[x_2+1][y_2+1]+=c b[x2+1][y2+1]+=c是让整个 a a a数组中红色矩形面积的元素再加上 c c c,红色内的相当于被减了两次,再加上一次 c c c,才能使其恢复

前缀和与差分参考文章

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值