一维前缀和
输入一个数组 arr ,构造前缀数组 pre ,pre数组第 i 位代表的是,arr 数组前 i 个元素的和。
构造前缀和:
pre[i] = pre[i-1] + arr[i]
那么,如果我们求arr数组中 从 L 到 R 范围的和,那么就可以用 pre [ R ] - pre [ L - 1 ] 。
#include <iostream>
#include <vector>
using namespace std;
int main(){
int n,m; cin>>n>>m;
vector<int> data;
for(int i = 0;i<n;++i){
int tp;
cin>>tp;
data.push_back(tp);
}
vector<int> pre(n+1,0);
for(int i = 1;i<=n;++i){
pre[i] = pre[i-1] + data[i-1];
}
while(m--){
int l,r; cin>>l>>r;
cout<< pre[r] - pre[l-1]<<endl;
}
return 0;
}
二维前缀和
与一维前缀和的思想相似,输入一个二维矩阵 arr[][] , 前缀和矩阵 pre [ i ][ j ] 代表的就是 arr 矩阵从0,0 到 i,j这么一个子矩阵的和。
构造前缀和:
pre[i][j] = pre[i-1][j] + pre[i][j-1] - pre[i-1][j-1] + arr[i][j]
如果我们想求 arr 数组从 (x1,y1) 到 (x2,y2) 子矩阵的和,就能用
pre [x2][y2] - pre [x2][y1-1] - pre [x1-1][y2] + pre [x1-1][y1-1] 实现
#include <iostream>
#include <vector>
using namespace std;
int main(){
int n,m,q; cin>>n>>m>>q;
vector<vector<int>> data;
for(int i = 0; i< n;++i){
vector<int> tparr;
for(int j = 0;j<m;++j){
int tp; cin>>tp;
tparr.push_back(tp);
}
data.push_back(tparr);
}
vector<vector<int>> preSum(n+1,vector<int>(m+1,0));
for(int i = 1; i <= n; ++i)
for(int j = 1 ; j <= m ; ++j)
preSum[i][j] = preSum[i-1][j] + preSum[i][j-1] - preSum[i-1][j-1] + data[i-1][j-1];
while(q--){
int x1,y1,x2,y2;
cin>>x1>>y1>>x2>>y2;
cout<< preSum[x2][y2] - preSum[x2][y1-1] - preSum[x1-1][y2] + preSum[x1-1][y1-1] <<endl;
}
return 0;
}
一维差分
输入arr = [ 1 2 2 1 2 1],那么,一个差分数组dif,dif[i]代表的就是arr[i] 和 arr[i-1]的差。
其中,dif[0] = arr[0],所以,我们就可以通过对dif数组累加(前缀和)的方式,重新构造出arr。
因为是重新构造arr是累加的形式,所以,一旦对dif中的某个位置dif[i]的值进行了修改,那每个包含dif[i]的前缀和都会跟着被修改,表现在,dif[i]被修改,dif[i ~ n]都被做了相同的修改。
dif数组的构造:
dif[i] = arr[i] - arr[i-1]
如果我们希望对arr中 L 到 R 位置上的值都+c,就可以通过对dif[L]+c实现arr[L~n]的值+c,然后通过dif[R+1]-c,实现arr[ L~R ]位置上的值+c。
在代码实现中,为了方便起见,对输入数组和差分数组的大小都进行了+1,把0位置空出来。这样在构造dif矩阵的时候就不需要特判了。
#include <iostream>
using namespace std;
const int N = 1e5+10;
int dd[N], dif[N];
int main(){
int n,m; cin>>n>>m;
for(int i = 1;i <= n ; ++ i) cin>>dd[i];
for(int i = 1;i <=n ; ++ i) dif[i] = dd[i] - dd[i-1];
while(m--){
int l, r, c; cin >> l >> r >> c;
dif[l] += c;
dif[r+1] -= c;
}
for(int i = 1; i <= n ; ++ i){
dif[i] += dif[i-1];
}
for(int i = 1 ; i <= n ; ++i){
cout<< dif[i] << ' ';
}
return 0;
}
二维差分矩阵
输入arr[][] ,那么,一个差分矩阵dif[][] 代表的就是 arr[i][j] 是差分矩阵0,0 ~ i,j 子矩阵的和。
所以,我们也可以通过对dif数组求前缀和的方式,重新构造出arr。
因为是重新构造arr是累加的形式,所以,一旦对dif中的某个位置dif[i][j]的值进行了修改,那每个包含dif[i][j]的前缀和都会跟着被修改,表现在,dif[i][j]被修改,i,j ~ n,m的子矩阵都被做了相同的修改。
dif矩阵的构造:
因为arr是dif的前缀和,所以由前缀和矩阵公式,arr[i][j] = arr[i-1][j] + arr[i][j-1] - arr[i-1][j-1] + dif[i][j]
则 dif[i][j] = arr[i][j] - arr[i-1][j] - arr[i][j-1] + arr[i-1][j-1]
如果我们希望对arr中 (x1,y1) 到 (x2,y2) 位置上的值都+c,就可以通过
对dif[x1][y1]+c实现x1,y1~n,m的子矩阵+c,
然后通过dif[x1][y2+1] - c,dif[x2+1][y1] - c, dif[x2+1][y2+1] +c 实现子矩阵(x1,y1) 到 (x2,y2) 位置上的值都+c。
在代码实现中,为了方便起见,对输入矩阵和差分矩阵的行列大小都进行了+1,把0位置空出来。这样在构造dif矩阵的时候就不需要特判了。
#include <iostream>
using namespace std;
const int MAXLEN = 1e3+10;
int arr[MAXLEN][MAXLEN], dif[MAXLEN][MAXLEN];
int main(){
int n, m, q; cin >> n >> m >> q;
for(int i = 1;i <= n; ++i){
for(int j = 1;j <= m; ++j){
cin>>arr[i][j];
dif[i][j] = arr[i][j] - arr[i-1][j] - arr[i][j-1] + arr[i-1][j-1];
}
}
while(q--){
int x1, y1, x2, y2, c;
cin>> x1 >> y1 >> x2 >> y2 >> c;
dif[x1][y1] += c;
dif[x1][y2+1] -= c;
dif[x2+1][y1] -= c;
dif[x2+1][y2+1] += c;
}
for(int i = 1;i <= n; ++i){
for(int j = 1;j <= m; ++j){
dif[i][j] = dif[i-1][j] + dif[i][j-1] - dif[i-1][j-1] + dif[i][j];
cout<<dif[i][j]<<' ';
}
cout<<endl;
}
return 0;
}
#差分的细节
无论是一维还是二维,从差分构造原始数据的时候,要确保前一次的求和能保存下来,存在arr中还是dif中无所谓,根据题意要求就行,比如:
对于一维,可以直接保存在dif中,这样会破坏dif
dif[i] = dif[i-1] + dif[i]
也可以保存在arr中,这样更清晰
arr[i] = arr[i-1] + dif[i]
对于二维,可以直接保存在dif中,这样会破坏dif
dif[i][j] = dif[i-1][j] + dif[i][j-1] - dif[i-1][j-1] + dif[i][j];
也可以保存在arr中,这样更清晰
arr[i][j] = arr[i-1][j] + arr[i][j-1] - arr[i-1][j-1] + dif[i][j]