前缀和
学习参考视频:STUACM-算法入门-前缀和与差分(含二维)_哔哩哔哩_bilibili
一维前缀和
前缀和:给定数组 a r r [ 0.. N ] arr[0..N] arr[0..N], s u m [ i ] sum[i] sum[i]表示 a r r [ 0... i ] arr[0...i] arr[0...i]区间和。
{ s u m [ 0 ] = a r r [ 0 ] , i = 0 s u m [ i ] = s u m [ i − 1 ] + a r r [ i ] , i > 0 \begin{cases} sum[0]=arr[0] &,i=0 \\ sum[i] = sum[i-1]+arr[i]&,i>0 \\ \end{cases} {sum[0]=arr[0]sum[i]=sum[i−1]+arr[i],i=0,i>0
实际应用——区间和:sum(L,R) 表示
a
r
r
[
L
.
.
.
R
]
arr[L...R]
arr[L...R]的区间和。暴力计算时间复杂度为
O
(
N
)
O(N)
O(N)
s
u
m
(
L
,
R
)
=
{
s
u
m
[
R
]
,
L
=
0
s
u
m
[
R
]
−
s
u
m
[
L
−
1
]
,
L
>
0
sum(L,R) = \begin{cases} sum[R]&,L=0 \\ sum[R]-sum[L-1] &,L>0\\ \end{cases}
sum(L,R)={sum[R]sum[R]−sum[L−1],L=0,L>0
一维差分
区间加法: add(L,R,v): a r r [ L . . . R ] arr[L...R] arr[L...R]的每个数加上v
差分数组: d d d为 a c c acc acc的差分数组,且 d d d的前缀和 s u m d sum_d sumd即为原数组 a c c acc acc。
d [ i ] = { a r r [ i ] − a r r [ i − 1 ] , i > 0 a r r [ i ] , i = 0 d[i] = \begin{cases} arr[i] - arr[i-1] &,i>0 \\ arr[i] &,i=0\\ \end{cases} d[i]={arr[i]−arr[i−1]arr[i],i>0,i=0
实际应用——累计区间加法:区间操作add(L,R,v) 等价于 d [ L ] + v , d [ R + 1 ] = v d[L]+v, d[R+1]=v d[L]+v,d[R+1]=v
-
原因分析
-
d[L]+v,是指arr[L…N]都加上v
-
d[R+1]-v,是指arr[R+1…N]都减去v,以抵消d[L]+v对[R+1…N]的影响
-
-
复杂度分析
数组长度N,M次操作,则为 O ( M + N ) O(M+N) O(M+N) -
应用场景:
-
多次操作一次询问:差分数组
-
一次操作一次询问:线段树
-
一般情况下,不会基于arr记录结果差分,而是基于全0数组记录操作差分,通过前缀和得到总的操作 Δ d \Delta d Δd,原数组最后的结果为 a r r + Δ d arr + \Delta d arr+Δd
二维前缀和
给定矩阵 M [ n × m ] M[n\times m] M[n×m]
矩阵和:sum(a,b,c,d),(a,b)到(c,d)的和,即以(a,b)和(c,d)为对角顶点的矩阵和,暴力计算时间复杂度为 O ( n m ) O(nm) O(nm)
定义:
s
u
m
[
i
]
[
j
]
sum[i][j]
sum[i][j] 是从(0,0)到(i,j)的和
{
s
u
m
[
0
]
[
j
]
=
s
u
m
[
0
]
[
j
−
1
]
+
M
[
0
]
[
j
]
,
i
=
0
,
j
≠
0
s
u
m
[
i
]
[
0
]
=
s
u
m
[
i
−
1
]
[
0
]
+
M
[
i
]
[
0
]
,
i
≠
0
,
j
=
0
s
u
m
[
i
]
[
j
]
=
M
[
i
]
[
j
]
+
s
u
m
[
i
−
1
]
[
j
]
+
s
u
m
[
i
]
[
j
−
1
]
−
s
u
m
[
i
−
1
]
[
j
−
1
]
,
o
t
h
e
r
\begin{cases} sum[0][j] = sum[0][j-1]+M[0][j]&,i=0,j\neq 0\\ sum[i][0] = sum[i-1][0]+M[i][0]&,i\neq0,j= 0\\ sum[i][j] = M[i][j] + sum[i-1][j] + sum[i][j-1] - sum[i-1][j-1]&,other \\ \end{cases}
⎩⎪⎨⎪⎧sum[0][j]=sum[0][j−1]+M[0][j]sum[i][0]=sum[i−1][0]+M[i][0]sum[i][j]=M[i][j]+sum[i−1][j]+sum[i][j−1]−sum[i−1][j−1],i=0,j=0,i=0,j=0,other
矩阵和
s
u
m
(
x
1
,
y
1
,
x
2
,
y
2
)
=
{
s
u
m
[
x
2
]
[
y
2
]
−
s
u
m
[
x
2
]
[
y
1
−
1
]
,
x
1
=
0
s
u
m
[
x
2
]
[
y
2
]
−
s
u
m
[
x
1
−
1
]
[
y
2
]
,
y
1
=
0
s
u
m
[
x
2
]
[
y
2
]
−
s
u
m
[
x
1
−
1
]
[
y
1
]
,
y
1
=
y
2
s
u
m
[
x
2
]
[
y
2
]
−
s
u
m
[
x
1
−
1
]
[
y
2
]
−
s
u
m
[
x
2
]
[
y
1
−
1
]
+
s
u
m
[
x
1
−
1
]
[
y
1
−
1
]
,
o
t
h
e
r
sum(x1,y1,x2,y2) = \begin{cases} sum[x2][y2]-sum[x2][y1- 1]&,x1=0 \\ sum[x2][y2] - sum[x1-1][y2]&,y1=0\\ sum[x2][y2]-sum[x1-1][y1]&,y1=y2 \\ sum[x2][y2] - sum[x1-1][y2]-sum[x2][y1- 1]+sum[x1-1][y1-1]&,other \end{cases}
sum(x1,y1,x2,y2)=⎩⎪⎪⎪⎨⎪⎪⎪⎧sum[x2][y2]−sum[x2][y1−1]sum[x2][y2]−sum[x1−1][y2]sum[x2][y2]−sum[x1−1][y1]sum[x2][y2]−sum[x1−1][y2]−sum[x2][y1−1]+sum[x1−1][y1−1],x1=0,y1=0,y1=y2,other
void pre_sum(int*){
sum[0][0] = M[0][0]; // 第一个
for(int i=1;i<n;i++) sum[i][0] = sum[i-1][0]+M[i][0]; // 第一列
for(int j=1;j<m;j++) sum[0][j] = sum[0][j-1]+M[0][j]; // 第一行
for(int i=1;i<n;i++)
for(int j=1;j<m;j++)
sum[i][j] = sum[i-1][j] + sum[i][j-1] - sum[i-1][j-1] + M[i][j];
}
void get_sum(int x1,int y1, int x2, int y2){
if(!x1 && !y1) return sum[x2][y2];
if(!x1) return sum[x2][y2]-sum[x2][y1-1];
if(!y1) return sum[x2][y2]-sum[x1-1][y2];
return sum[x2][y2]-sum[x2][y1-1]-sum[x1-1][y2]+sum[x1-1][y1-1];
}
二维差分
add(x1,y1,x2,y2,v)表示以(x1,y1)和(x2,y2)为对角顶点的部分同时加上v
以下基于全0数组记录操作差分:(原理和一维差分类似,就不列公式了)
int d[n+1][m+1] = {0}; // 边界扩大1,避免右/下边界判断
void add(x1,y1,x2,y2,v){
d[x1][y1]+=v;
d[x2+1][y1]-=v;
d[x1][y2+1]-=v;
d[x2+1][y2+1]+=v;
}
// 操作结束后,对d求pre_sum,得到delta_d,M+=delta_d即为操作结果