前缀和与差分
一.前缀和
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[l−1],其中 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[l−1]+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[l−1]=a[1]+a[2]+⋯+a[l−1];
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[l−1]=a[l]+a[l+1]+⋯+a[r];
3.图解
如上图所示,
S
[
r
]
S[r]
S[r]与
S
[
l
−
1
]
S[l-1]
S[l−1]有许多重复元素,因此可以进行相减来求取任一段落的和值。
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[i−1][j]+S[i][j−1]−S[i−1][j−1]+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[x1−1][y2]−S[x2][y1−1]+S[x1−1][y1−1]
3.图解
上图是二维数组前缀和的图像表示。
下图是对
S
[
i
]
[
j
]
S[i][j]
S[i][j]的推导:
紫色面积是指
(
1
,
1
)
(1,1)
(1,1)左上角到
(
i
,
j
−
1
)
(i,j-1)
(i,j−1)右下角的矩形面积,绿色面积是指
(
1
,
1
)
(1,1)
(1,1)左上角到
(
i
−
1
,
j
)
(i-1, j)
(i−1,j)右下角的矩形面积,红色面积是指
(
1
,
1
)
(1,1)
(1,1)
左上角到
(
i
−
1
,
j
−
1
)
(i-1,j-1)
(i−1,j−1)右下角的矩形面积,蓝色面积是指
(
i
−
1
,
j
−
1
)
(i-1,j-1)
(i−1,j−1)左上角到
(
i
,
j
)
(i,j)
(i,j)右下角的矩形面积。
从图中我们很容易看出,整个外围蓝色矩形面积
S
[
i
]
[
j
]
S[i][j]
S[i][j] = 绿色面积
S
[
i
−
1
]
[
j
]
S[i-1][j]
S[i−1][j] + 紫色面积
S
[
i
]
[
j
−
1
]
S[i][j-1]
S[i][j−1] - 红色面积
S
[
i
−
1
]
[
j
−
1
]
S[i-1][j-1]
S[i−1][j−1] + 小蓝色面积
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)
(x1−1,y2)右下角的矩形面积 ,棕色面积是指
(
1
,
1
)
(1,1)
(1,1)左上角到
(
x
2
,
y
1
−
1
)
(x_2,y_1-1)
(x2,y1−1)右下角的矩形面积,红色面积是指
(
1
,
1
)
(1,1)
(1,1)左上角到
(
x
1
−
1
,
y
1
−
1
)
(x_1-1,y_1-1)
(x1−1,y1−1)右下角的矩形面积,绿色面积是指
(
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][y1−1] - 紫色面积
S
[
x
1
−
1
]
[
y
2
]
S[x_1- 1][y_2]
S[x1−1][y2] + 红色面积
S
[
x
1
−
1
]
[
y
1
−
1
]
S[x_1-1][y_1-1]
S[x1−1][y1−1]。
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[+=c,b[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[i−1]
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(n∗m)。
而如果考虑差分做法,我们只需要进行两步操作:
首先让差分数组
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[i−1][j]−a[i][j−1]+a[i−1][j−1]
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,才能使其恢复
前缀和与差分参考文章