差分与前缀和
1. 一维前缀和 :
给你一个数列 :a1 , a2 , a3 , a4 , ....... , an 。 以及 k 次询问。 每一次询问给出两个数 L 、R ,要求你回答这个数列的 [ L , R ] 区间内所有数的和是多少 (即 sum( L,R ) )
解法 : 1. 暴力。每次询问都用一层 for 循环求解。查询复杂度为 O( n*k )
2.一维前缀和: 另外开一个与存储数列的数组等长度的 数组 dp[N] 。dp[i] 表示 sum(1,i) 。
之后对于 k 次询问,只需要用 dp[R] - dp[L-1] 表示。查询复杂度为O(1)
1 int arr[100+1]; 2 int dp[100+1]; 3 int n,k,l,r; 4 cin >> n >> k; 5 for(int i = 1; i <= n; i++){ 6 cin >> arr[i]; 7 dp[i] = dp[i-1] + arr[i]; 8 } 9 while(k--){ 10 cin >> l >> r; 11 cout<<dp[r] - dp[l-1]; 12 }
2.差分(一维):
在上一个问题的基础上,再附加:在 k 次询问以前 , 还有这样一系列操作( 设一共进行了 t 次 ) :
(1)给定 L 、R , 对原数列的 [ L , R ] 每一个数增加 p
(2)给定 L 、R , 对原数列的 [ L , R ]每一个数减少 p
最后再进行之前的k次询问
解法:
新建一个数组 m [ N ] , 引进一个变量 add 来记录实时变化。具体看代码。
1 for(i = 1;i <= t ; i++){ 2 int L,R,cmd; 3 cin >> cmd >> L >> R >> p; 4 if(cmd == 1){ 5 m[L]+=p;m[R+1]-=p; 6 } 7 else{ 8 m[L]-=p;m[R+1]+=p; 9 } 10 } 11 int add=0; 12 for(i=1;i<=n;i++){ 13 add+=b[i]; 14 dp[i]= dp[i-1]+ arr[i] + add; 15 }
我们假设 t 就等于 1 ( 即只进行了一次修改 )。 方便我们理解差分的工作原理。
假设 N = 10 , 即数列共有10项, 我们现在对区间[ 3,6 ] 的每一个数进行 +1 操作。 这对应于代码的第4行: m[3] += 1 , m[7] -= 1
接下来进入第 11 行, add 初始为 0。
然后进入循环:
* 当1 <= i <= 2 时 , add += 0 dp [i] = dp[i-1] + arr[i] + add = ap[i-1] + arr[i] + 0
* 当 i = 3 时 , add += b[3] -> add = add + 1 = 1
故 3 <= i <= 6 时 ,dp [i] = dp[i-1] + arr[i] + add = ap[i-1] + arr[i] + 1
* 当 i = 7 时 , add += b[7] -> add = add - 1 = 0
故 7 <= i <= 10 时 dp [i] = dp[i-1] + arr[i] + add = ap[i-1] + arr[i] + 0
利用差分 , 我们将 t 次区间修改的复杂度从O( t * n )降到了 O( t ) , 而对dp数组的处理复杂度没变。
ps : 如果题目改成边修改边询问,就是用线段树来求解了。这不属于本次讨论的范畴。
3.二维前缀和:
贴一个网上找到的图 : 图片来源:https://blog.csdn.net/qq_34990731/article/details/82807870
再推荐一个比较精致的博客:
https://www.cnblogs.com/LMCC1108/p/10753451.html
我们用二维数组 arr[ m ][ n ] 来存储输入所给出的矩阵各个点的数值,并用一个二维数组 dp[ m ][ n ] 来表示 以点 ( 1,1 ) 为左上角 , 点 ( i , j ) 为右下角的矩形区间和。
易得状态转移方程 : dp[ i ][ j ] = dp[ i - 1][ j ] + dp[ i ][ j - 1 ] - dp[ i - 1][ j - 1] + arr[ i ][ j ]
在接下来的询问中 , 每次询问给出 四个数 x1, y1, x2, y2 。 要求你给出包含点 ( x1,y1 ) 、(x2 , y2 )在内的 , 且以( x1, y1 ) 为左上角 , 点( x2 , y2 )为右下角的子矩阵区间和
1 /* 2D-sum */ 2 #include <iostream> 3 #define Maxsize 1000+1 4 using namespace std; 5 int map[Maxsize][Maxsize]; 6 int dp[Maxsize][Maxsize]; 7 int main(){ 8 /* read */ 9 int n,m; 10 cin >> n >> m; 11 for(int i = 1; i <= n; i++) 12 for(int j = 1; j <= m; j++) 13 cin >> map[i][j]; 14 15 /* init dp array */ 16 for(int i = 1; i <= n; i++) 17 for(int j = 1; j <= m; j++) 18 dp[i][j] = dp[i-1][j] + dp[i][j-1] - dp[i-1][j-1] + map[i][j]; 19 20 /* query */ 21 int q; 22 int x1,y1,x2,y2; 23 /* 24 (x1,y1) -> the upper left corner 25 (x2,y2) -> the lower right corner 26 */ 27 cin >> q; 28 while(q--){ 29 cin >> x1 >> y1 >> x2 >> y2; 30 x1--;y1--; 31 // 有一点注意,因为画图和定义原因我们发现边 32 // 界好像不对,我们来看看,我们定义的状态是 33 // 整个矩阵包括边的和,而我们要求的也是要包括边的,所以我们要让x1--,y1-- 34 cout<<"output: "<<dp[x2][y2] - dp[x1][y2] - dp[x2][y1] + dp[x1][y1]<<endl; 35 } 36 return 0; 37 }
4.差分(二维):
1 #include <cstdio> 2 #include <iostream> 3 #define Maxsize 5+1 4 using namespace std; 5 int m,n; 6 int map[Maxsize][Maxsize]; 7 int dp[Maxsize][Maxsize]; 8 int diff[Maxsize][Maxsize]; 9 void init_map(){ 10 cin >> m >> n; 11 for(int i = 1; i <= m; i++) 12 for(int j = 1; j <= n; j++) 13 cin >> map[i][j]; 14 } 15 void add(int x1,int y1,int x2,int y2,int val){ 16 diff[x1][y1] += val; 17 diff[x1][y2+1] -= val; 18 diff[x2+1][y1] -= val; 19 diff[x2+1][y2+1] += val; 20 } 21 void init_dp(){ 22 for(int i = 1; i <= m; i++){ 23 for(int j = 1; j <= n; j++){ 24 diff[i][j] += diff[i-1][j] + diff[i][j-1] - diff[i-1][j-1]; 25 dp[i][j] = dp[i-1][j] + dp[i][j-1] - dp[i-1][j-1] + map[i][j] + diff[i][j]; 26 } 27 } 28 } 29 void query(int x1,int y1,int x2,int y2){ 30 x1--;y1--; 31 cout << dp[x2][y2] - dp[x2][y1] - dp[x1][y2] + dp[x1][y1] << endl; 32 } 33 int main(){ 34 init_map(); 35 int k,t; 36 int x1,y1,x2,y2,val; 37 cin >> k; 38 while (k--) { 39 cin >> x1 >> y1 >> x2 >> y2 >> val; 40 add(x1,y1,x2,y2,val); 41 } 42 init_dp(); 43 cin >> t; 44 while (t--) { 45 cin >> x1 >> y1 >> x2 >> y2; 46 query(x1,y1,x2,y2); 47 } 48 return 0; 49 }
5.利用 hash 将二维前缀和问题降维处理:
当题目中给的矩阵过大的时候,如果强行开二维数组存矩阵就会MLE , 此时如果用 hash 就可以减少空间。
例: HDU 6514 Monitor https://vjudge.net/problem/HDU-6514
注 : 这个题的评测机不知道为什么不对全局数组做默认初始化,因此直接交这个代码会判WA。实际上这个也是对的。
如果交的化记得在while循环开始时fill map 数组置 0 。
1 #include <cstdio> 2 const int maxn = 20000010; 3 int map[maxn]; 4 int n,m,k,q; 5 using namespace std; 6 inline int Hash(int x,int y){ 7 return x * (m + 1) + y; 8 } 9 inline void add(int x1,int y1,int x2,int y2){ 10 map[Hash(x1,y1)]++; map[Hash(x1,y2+1)]--; 11 map[Hash(x2+1,y1)]--; map[Hash(x2+1,y2+1)]++; 12 } 13 int main(){ 14 while (~scanf("%d %d",&n,&m)) { 15 int x1,y1,x2,y2; 16 n++; m++; // why ? 17 scanf("%d",&k); 18 for(int i = 1; i <= k; i++){ 19 scanf("%d %d %d %d",&x1,&y1,&x2,&y2); 20 add(x1,y1,x2,y2); 21 } 22 for(int i = 1; i <= n; i++) 23 for(int j = 1; j <= m; j++) 24 map[Hash(i,j)] += map[Hash(i-1,j)] + map[Hash(i,j-1)] - map[Hash(i-1,j-1)]; 25 26 for(int i = 1; i <= n; i++) 27 for(int j = 1; j <= m; j++) 28 map[Hash(i,j)] = (bool)map[Hash(i,j)]; 29 30 for(int i = 1; i <= n; i++) 31 for(int j = 1; j <= m; j++) 32 map[Hash(i,j)] += map[Hash(i-1,j)] + map[Hash(i,j-1)] - map[Hash(i-1,j-1)]; 33 34 scanf("%d",&q); 35 while(q--){ 36 scanf("%d %d %d %d",&x1,&y1,&x2,&y2); 37 int ans = map[Hash(x2,y2)] - map[Hash(x1-1,y2)] - map[Hash(x2,y1-1)] + map[Hash(x1-1,y1-1)]; 38 if(ans == (x2-x1+1) * (y2-y1+1)) 39 puts("YES"); 40 else 41 puts("NO"); 42 } 43 } 44 return 0; 45 }