差分与前缀和

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 }

 

posted @ 2020-03-01 14:46  popozyl  阅读(469)  评论(0编辑  收藏  举报