• 差分与前缀和


    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 }
    ---- suffer now and live the rest of your life as a champion ----
  • 相关阅读:
    结对第二次作业——某次疫情统计可视化的实现
    结对第一次—疫情统计可视化(原型设计)
    寒假作业(2/2)——疫情统计
    软工实践寒假作业(1/2)
    3DMAX三维编辑命令FFD的使用
    个人作业——软件工程实践总结&个人技术博客
    个人作业——软件评测
    结对第二次作业——某次疫情统计可视化的实现
    软工实践寒假作业(2/2)
    软工实践寒假作业(1/2)
  • 原文地址:https://www.cnblogs.com/popodynasty/p/12389798.html
Copyright © 2020-2023  润新知