前缀和 差分
一种预处理技巧,可在获取数组元素时在线获取前缀和/差分数组,之后要用的时候即可用 O ( 1 ) O(1) O(1)的复杂度进行访问。
前缀和数组可在 O ( 1 ) O(1) O(1)的复杂度访问前 n n n项元素之和,差分是前缀和的逆运算,适合用于区间修改(先修改后查询)的问题。
前缀和、差分间的关系: d 差分 ⟶ ∑ a 原 ⟶ ∑ s 前缀和 \underset{差分}{d}\stackrel{\sum}{\longrightarrow}\underset{原}{a}\stackrel{\sum}{\longrightarrow}\underset{前缀和}{s} 差分d⟶∑原a⟶∑前缀和s
一般定义前缀和、差分数组时,通常选择浪费一个空间,以提升编码速度(无需考虑下标为负的情况)
一维前缀和 一维差分
-
一维前缀和: s [ i ] = s [ i − 1 ] + a [ i ] s[i]=s[i-1]+a[i] s[i]=s[i−1]+a[i]
-
一维差分: d [ i ] = a [ i ] − a [ i − 1 ] d[i]=a[i]-a[i-1] d[i]=a[i]−a[i−1]
区间修改:在差分数组中,对 d [ j ] d[j] d[j]进行修改后,相当于在原数组中修改了 [ j , + ∞ ] [j,+\infty] [j,+∞]范围内的值。若只在 [ j , k ] [j,k] [j,k]上进行区间修改,需在 d [ k + 1 ] d[k+1] d[k+1]处撤销修改(加上在第 d [ j ] d[j] d[j]处修改值的相反数)。
已在其他处获取 m a x , a 数组 , l 1 , r 1 , x , l 2 , r 2 max,a数组,l1,r1,x,l2,r2 max,a数组,l1,r1,x,l2,r2的数据。
extern int max,d[max+1],a[max+1],s[max+1];//max:数组最大长度 a:原数组 d:差分数组 s:前缀和数组
extern int l1,r1,x,l2,r2;//l:修改左区间 r:修改右区间 x:修改增量 l2:查询左区间 r2:查询右区间
- s和d数组元素获取
for(int i=1;i<=max;i++){//获取d和s数组可在获取a数组时在线获取
d[i]=a[i]-a[i-1];//获取差分数组
s[i]=s[i-1]+a[i];//获取前缀和数组
}
- 区间修改
d[l1]+=x;//在左区间处进行修改
d[r1+1]-=x;//在(右区间+1)处撤销修改
- 区间查询
for(int i=1;i<=max;i++){
a[i]=d[i]+a[i-1];//求出修改后的原数组
s[i]=a[i]+s[i-1];//求出修改后的前缀和数组
}
int ans=s[r2]-s[l2-1];//区间查询前缀和
二维前缀和 二维差分
-
二维前缀和: s [ j ] [ i ] = s [ j − 1 ] [ i ] 上方所有部分 + s [ j ] [ i − 1 ] 左方所有部分 − s [ j − 1 ] [ i − 1 ] 左上被加 2 次部分 + a [ j ] [ i ] 本身 s[j][i]=\underset{上方所有部分}{s[j-1][i]}+\underset{左方所有部分}{s[j][i-1]}-\underset{左上被加2次部分}{s[j-1][i-1]}+\underset{本身}{a[j][i]} s[j][i]=上方所有部分s[j−1][i]+左方所有部分s[j][i−1]−左上被加2次部分s[j−1][i−1]+本身a[j][i]
二维矩形区间内的前缀和:如:从 [ y , x ] [y,x] [y,x]到 [ j , i ] [j,i] [j,i]二维矩形范围内的前缀和: s = s [ j ] [ i ] − s [ y − 1 ] [ i ] 上方所有部分 − s [ j ] [ x − 1 ] 左方所有部分 + s [ y − 1 ] [ x − 1 ] 左上方被减 2 次部分 s=s[j][i]-\underset{上方所有部分}{s[y-1][i]}-\underset{左方所有部分}{s[j][x-1]}+\underset{左上方被减2次部分}{s[y-1][x-1]} s=s[j][i]−上方所有部分s[y−1][i]−左方所有部分s[j][x−1]+左上方被减2次部分s[y−1][x−1]
-
二维差分:
朴素方法(绝对法): d [ j ] [ i ] = a [ j ] [ i ] − a [ j − 1 ] [ i ] 上方所有部分 − a [ j ] [ i − 1 ] 左方所有部分 + a [ j − 1 ] [ i − 1 ] 左上方被减 2 次部分 d[j][i]=a[j][i]-\underset{上方所有部分}{a[j-1][i]}-\underset{左方所有部分}{a[j][i-1]}+\underset{左上方被减2次部分}{a[j-1][i-1]} d[j][i]=a[j][i]−上方所有部分a[j−1][i]−左方所有部分a[j][i−1]+左上方被减2次部分a[j−1][i−1]
等效大小为1的区间修改的获取方法(相对法):在n*m的矩阵中,d数组元素初始全为0,进行 ∑ j = 1 n ∑ i = 1 m \sum\limits_{j=1}^{n}\sum\limits_{i=1}^{m} j=1∑ni=1∑m循环:
-
d [ j ] [ i ] + = a [ j ] [ i ] d[j][i]+=a[j][i] d[j][i]+=a[j][i]
-
d [ j + 1 ] [ i ] − = a [ j ] [ i ] d[j+1][i]-=a[j][i] d[j+1][i]−=a[j][i]
-
d [ j ] [ i + 1 ] − = a [ j ] [ i ] d[j][i+1]-=a[j][i] d[j][i+1]−=a[j][i]
-
d [ j + 1 ] [ i + 1 ] + = a [ j ] [ i ] d[j+1][i+1]+=a[j][i] d[j+1][i+1]+=a[j][i]
区间修改:对 d [ y ] [ x ] d[y][x] d[y][x]进行修改后,在原数组中修改的是 [ y , x ] [y,x] [y,x]到 [ + ∞ , + ∞ ] [+\infty,+\infty] [+∞,+∞]范围矩形内的值。如,在 [ y , x ] [y,x] [y,x]到 [ j , i ] [j,i] [j,i]范围矩形内中每个元素加 x x x,需:
- d [ y ] [ x ] + = x d[y][x]+=x d[y][x]+=x:原数组 [ y , x ] [y,x] [y,x]到 [ + ∞ , + ∞ ] [+\infty,+\infty] [+∞,+∞]矩形范围中元素 + x +x +x
- d [ j + 1 ] [ x ] − = x d[j+1][x]-=x d[j+1][x]−=x:原数组 [ j + 1 , x ] [j+1,x] [j+1,x]到 [ + ∞ , + ∞ ] [+\infty,+\infty] [+∞,+∞]矩形范围中元素 − x -x −x(位于区间修改矩形下方)
- d [ y ] [ i + 1 ] − = x d[y][i+1]-=x d[y][i+1]−=x:原数组 [ y , i + 1 ] [y,i+1] [y,i+1]到 [ + ∞ , + ∞ ] [+\infty,+\infty] [+∞,+∞]矩形范围中元素 − x -x −x(位于区间修改矩形右方)
- d [ j + 1 ] [ x + 1 ] + = x d[j+1][x+1]+=x d[j+1][x+1]+=x:原数组 [ j + 1 , x + 1 ] [j+1,x+1] [j+1,x+1]到 [ + ∞ , + ∞ ] [+\infty,+\infty] [+∞,+∞]矩形范围中元素被减了2次,撤销此修改(位于区间修改矩形的右下方)
-
已在其他处获取 m , n , a 数组 , _ x 1 , _ x 2 , _ y 1 , _ y 2 m,n,a数组,\_x1,\_x2,\_y1,\_y2 m,n,a数组,_x1,_x2,_y1,_y2数据
extern int m,n,x1,y1,x2,y2,d[n+1][m+1],a[n+1][m+1],s[n+1][m+1];
//n,m:n*m矩阵的矩阵维度 x1,y1:区间修改起点坐标 x2,y2:区间修改终点坐标 d:差分数组 a:原数组 s:前缀和数组
- s和d数组元素的获取
for(int j=1;j<=n;j++){
for(int i=1;i<=m;i++){
s[j][i]=s[j-1][i]+s[j][i-1]-s[j-1][i-1]+a[j][i];//获取二维前缀和数组
}
}
for(int j=1;j<=n;j++){
for(int i=1;i<=m;i++){
d[j][i]=a[j][i]-a[j-1][i]-a[j][i-1]+a[j-1][i-1];//获取二维差分数组(朴素法)
}
}
//获取二维差分数组(相对法)
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];
}
}
- 区间查询
int ans=s[y2][x2]-s[y1-1][x2]-s[y2][x1-1]+s[y1][x1];
- 区间修改
d[y1][x1]+=x;
d[y1][x2+1]-=x;
d[y2+1][x1]-=x;
d[y2+1][x2+1]+=x;
for(int j=1;j<=n;j++){
for(int i=1;i<=m;i++){
a[j][i]=a[j-1][i]+a[j][i-1]-a[j-1][i-1]+d[j][i];//重新计算原数组
s[j][i]=s[j-1][i]+s[j][i-1]-s[j-1][i-1]+a[j][i];//重新计算前缀和数组
}
}