前缀和 差分
差分与前缀和为互逆运算,适用于区间修改+区间查询问题,可将区间修改转换为单点修改: d 差分 ⟶ ∑ a 原 ⟶ ∑ s 前缀和 \underset{差分}{d}\stackrel{\sum}{\longrightarrow}\underset{原}{a}\stackrel{\sum}{\longrightarrow}\underset{前缀和}{s} 差分d⟶∑原a⟶∑前缀和s
实现时通常数组下标从1开始,避免越界访问到负下标
一维前缀和 一维差分
几何意义:一维为线性的前缀运算,前缀和为长度
- 一维前缀和: s i = s i − 1 + a i ⇒ a i = s i − s i − 1 s_i=s_{i-1}+a_i\Rightarrow a_i=s_i-s_{i-1} si=si−1+ai⇒ai=si−si−1
- 第一类一维差分: d i = a i − a i − 1 ⇒ a i = a i − 1 + d i d_i=a_i-a_{i-1}\Rightarrow a_i=a_{i-1}+d_i di=ai−ai−1⇒ai=ai−1+di
int n,a[n+1],s[n+1],d[n+2];
void init(){
for(int i=1;i<=n;i++){
s[i]=s[i-1]+a[i];
d[i]=a[i]-a[i-1];
}
}
预处理的复杂度为 O ( n ) O(n) O(n)
区间修改
设操作区间为 [ L , R ] [L,R] [L,R]:
- 差分数组中 d L + = Δ d_L+=\Delta dL+=Δ的单点修改,等价于原数组 a i + = Δ ( i ∈ [ L , + ∞ ] ) a_i+=\Delta(i\in[L,+\infty]) ai+=Δ(i∈[L,+∞])的区间修改
- 若操作区间存在右端点 R R R,则需在 R + 1 R+1 R+1处撤销修改: d R + 1 − = Δ d_{R+1}-=\Delta dR+1−=Δ,相当于原数组 a i − = Δ ( i ∈ [ R + 1 , + ∞ ] ) a_i-=\Delta(i\in[R+1,+\infty]) ai−=Δ(i∈[R+1,+∞])
void modify(int l,int r,int d){
d[l]+=d,d[r+1]-=d;
}
区间修改的复杂度为 O ( 1 ) O(1) O(1)
区间查询
-
查询原数组:第二类一维差分: d d d初始化全部为0,用于存储原数组的每个元素的增量 Δ \Delta Δ。区间修改 [ L , R ] [L,R] [L,R]方法不变,之后求前缀和即为原数组每个元素的增量 Δ \Delta Δ
-
查询前缀和: ∑ i = L R a i = ∑ i = 1 R a i − ∑ i = 1 L − 1 a i \sum\limits_{i=L}^{R}a_i=\sum\limits_{i=1}^{R}a_i-\sum\limits_{i=1}^{L-1}a_i i=L∑Rai=i=1∑Rai−i=1∑L−1ai。
int query(int l,int r){
//第一类一维差分
for(int i=1;i<=n;i++){
a[i]=d[i]+a[i-1];//更新原数组
s[i]=a[i]+s[i-1];
}
return s[r]-s[l-1];
//第二类一维差分
for(int i=1;i<=n;i++){
d[i]+=d[i-1];
}
for(int i=1;i<=n;i++){
a[i]+=d[i];
s[i]=s[i-1]+a[i];
}
return s[r]-s[l-1];
}
区间查询复杂度为 O ( n ) O(n) O(n)。由此可见差分数组擅长处理区间修改,但不擅长单点查询。
二维前缀和 二维差分
几何意义:二维为面的前缀运算,前缀和为面积
-
二维前缀和: s [ i ] [ j ] = s [ i − 1 ] [ j ] 上侧所有部分 + s [ i ] [ j − 1 ] 左侧所有部分 − s [ i − 1 ] [ j − 1 ] 左上侧被加 2 次部分 + a [ i ] [ j ] 本身 s[i][j]=\underset{上侧所有部分}{s[i-1][j]}+\underset{左侧所有部分}{s[i][j-1]}-\underset{左上侧被加2次部分}{s[i-1][j-1]}+\underset{本身}{a[i][j]} s[i][j]=上侧所有部分s[i−1][j]+左侧所有部分s[i][j−1]−左上侧被加2次部分s[i−1][j−1]+本身a[i][j]
-
矩阵前缀和:设矩阵左上顶点为 ( x , y ) (x,y) (x,y),右下顶点为 ( i , j ) (i,j) (i,j): S = s [ i ] [ j ] − s [ x − 1 ] [ j ] 上侧非矩形部分 − s [ i ] [ y − 1 ] 左方非矩形部分 + s [ x − 1 ] [ y − 1 ] 左上方被减 2 次非矩形部分 S=s[i][j]-\underset{上侧非矩形部分}{s[x-1][j]}-\underset{左方非矩形部分}{s[i][y-1]}+\underset{左上方被减2次非矩形部分}{s[x-1][y-1]} S=s[i][j]−上侧非矩形部分s[x−1][j]−左方非矩形部分s[i][y−1]+左上方被减2次非矩形部分s[x−1][y−1]
-
第一类二维差分: d [ i ] [ j ] = a [ i ] [ j ] − a [ i − 1 ] [ j ] 上侧所有部分 − a [ i ] [ j − 1 ] 左侧所有部分 + a [ i − 1 ] [ j − 1 ] 左上方被减 2 次部分 d[i][j]=a[i][j]-\underset{上侧所有部分}{a[i-1][j]}-\underset{左侧所有部分}{a[i][j-1]}+\underset{左上方被减2次部分}{a[i-1][j-1]} d[i][j]=a[i][j]−上侧所有部分a[i−1][j]−左侧所有部分a[i][j−1]+左上方被减2次部分a[i−1][j−1]
二维差分较为抽象,从前缀和与差分为互逆运算角度理解
int n,m,a[n+1][m+1],s[n+1][m+1],d[n+1][m+1];//n行m列
void init(){
for(int i=1;j<=n;j++)
for(int j=1;i<=m;i++){
s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];
d[i][j]=a[i][j]-a[i-1][j]-a[i][j-1]+a[i-1][j-1];//第一类二维差分
}
}
区间修改
设操作矩阵左上角坐标为 [ x 1 , y 1 ] [x_1,y_1] [x1,y1],右下角坐标为 [ x 2 , y 2 ] [x_2,y_2] [x2,y2]:
差分数组对 d [ i ] [ j ] + = Δ d[i][j]+=\Delta d[i][j]+=Δ的单点修改,等价于在原数组中的 [ i , j ] [i,j] [i,j]到 [ + ∞ , + ∞ ] [+\infty,+\infty] [+∞,+∞]范围的区间修改。因此, [ x 1 , y 1 ] [x_1,y_1] [x1,y1]到 [ x 2 , y 2 ] [x_2,y_2] [x2,y2]内中每个元素加 Δ \Delta Δ:
- d [ y 1 ] [ x 1 ] + = Δ d[y_1][x_1]+=\Delta d[y1][x1]+=Δ:原数组 [ y 1 , x 1 ] [y_1,x_1] [y1,x1]到 [ + ∞ , + ∞ ] + Δ [+\infty,+\infty]+\Delta [+∞,+∞]+Δ的二维区间修改
- d [ y 2 + 1 ] [ x 1 ] − = Δ d[y_2+1][x_1]-=\Delta d[y2+1][x1]−=Δ:原数组 [ y 2 + 1 , x 1 ] [y_2+1,x_1] [y2+1,x1]到 [ + ∞ , + ∞ ] [+\infty,+\infty] [+∞,+∞]矩形中元素 − Δ -\Delta −Δ(矩形右侧无需修改)
- d [ y 1 ] [ x 2 + 1 ] − = Δ d[y_1][x_2+1]-=\Delta d[y1][x2+1]−=Δ:原数组 [ y 1 , x 2 + 1 ] [y_1,x_2+1] [y1,x2+1]到 [ + ∞ , + ∞ ] [+\infty,+\infty] [+∞,+∞]矩形中元素 − Δ -\Delta −Δ(矩形下方无需修改)
- d [ y 2 + 1 ] [ x 2 + 1 ] + = Δ d[y_2+1][x_2+1]+=\Delta d[y2+1][x2+1]+=Δ:原数组 [ y 2 + 1 , x 2 + 1 ] [y_2+1,x_2+1] [y2+1,x2+1]到 [ + ∞ , + ∞ ] [+\infty,+\infty] [+∞,+∞]矩形中元素被减了2次 Δ \Delta Δ,撤销一次修改(矩形右下方)
void modify(int x1,int y1,int x2,int y2,int d){
d[y1][x1]+=d;
d[y2+1][x1]-=d;
d[y1][x2+1]-=d;
d[y2+1][x2+1]+=d;
}
区间查询
- 查询原数组:第二类二维差分: d d d全部初始化为0,用于存储原数组内每个元素的增量 Δ \Delta Δ。区间修改方法不变,之后求前缀和即为原数组每个元素的增量 Δ \Delta Δ。
- 查询前缀和
int query(int x1,int y1,int x2,int y2){
//第一类差分数组
for(int j=1;j<=n;j++)
for(int i=1;i<=m;i++){
a[i][j]=a[i-1][j]+a[i][j-1]-a[i-1][j-1]+d[i][j];//重新计算原数组
s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];
}
return s[y2][x2]-s[y1-1][x2]-s[y2][x1-1]+s[y1][x1];
//第二类差分数组
//第一类差分数组
for(int j=1;j<=n;j++)
for(int i=1;i<=m;i++)
d[i][j]+=d[i-1][j]+d[i][j-1]-d[i-1][j-1];
for(int j=1;j<=n;j++)
for(int i=1;i<=m;i++)
a[i][j]+=d[i][j];
}
-
技巧:等价于将原数组所有值视为 d d d数组增量。元素初始全为0,进行 ∑ i = 1 n ∑ j = 1 m \sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m} i=1∑nj=1∑m循环:
-
d [ i ] [ j ] + = a [ i ] [ j ] d[i][j]+=a[i][j] d[i][j]+=a[i][j]
-
d [ i + 1 ] [ j ] − = a [ i ] [ j ] d[i+1][j]-=a[i][j] d[i+1][j]−=a[i][j]
-
d [ i ] [ j + 1 ] − = a [ i ] [ j ] d[i][j+1]-=a[i][j] d[i][j+1]−=a[i][j]
-
d [ i + 1 ] [ j + 1 ] + = a [ i ] [ j ] d[i+1][j+1]+=a[i][j] d[i+1][j+1]+=a[i][j]
-
for(int j=1;j<=n;j++){
for(int i=1;i<=m;i++){
d[j][i]+=a[j][i];
d[j+1][i]-=a[j][i];
d[j][i+1]-=a[j][i];
d[j+1][i+1]+=a[j][i];
}
}
更高维的前缀和:三维前缀和,为对空间的前缀运算,几何意义为体积