C++前缀和 和 差分的学习

原文链接:前缀和与差分 图文并茂 超详细整理(全网最通俗易懂)_林小鹿@的博客-CSDN博客_前缀和差分

1.前缀和的定义

  • 前缀和是指某序列的前n项和,可以把它理解为数学上的数列的前n项和,而差分可以看成前缀和的逆运算。合理的使用前缀和与差分,可以将某些复杂的问题简单化。

    (数学角度上分析就是说,给定一数列an,其前n项和为Sn,那么Sn构成的数列是an数列的前缀和数列;而反过来,an数列被称为是Sn数列的差分数列。这种关系就像是导数和不定积分一样,求导后的函数是原函数的导数;原函数是求导后函数的不定积分。)

2.怎么利用前缀和

  • 给出一个问题:

    输入一个长度为n的整数序列。接下来再输入m个询问,每个询问输入一对l, r。对于每个询问,输出原序列中从第l个数到第r个数的和。

  • 法一:暴力解法

    先输入长度为n的数组,对数组进行遍历,每一次遍历找出对应的位置元素进行相加,遍历m次

    代码如下:

     #include <iostream>
     using namespace std;
     ​
     int main()
     {
         int n,m;cin>>n>>m;
         int a[100010];
         for(int i =1;i<=n;i++)
         {
             cin<<a[i];
         }
         while(m--)
         {
             int l,r,sum=0;cin>>l>>r;
             for(int i=l;i<=r;i++)
             {
                 sum+=a[i];
             }
             cout<<sum;
         }
     }

    (这样的时间复杂度为O(n * m),如果n和m的数据量稍微大一点就有可能超时,而我们如果使用前缀和的方法来做的话就能够将时间复杂度降到O(n + m),大大提高了运算效率。)

  • 法二:前缀和法

    先创建一个数组S[n]用于存放前缀和,接着在输入数据an时对数据进行处理,即求出对应的Sn,放进数组S[n]中;紧接着就是输入l和r,每输入一对l,r就求出S[r+1]-S[l]的值,所得值就是所求的区间值

    代码如下:

     #include <iostream>
     using namespace std;
     ​
     int main()
     {
         int S[100010];
         int n,m;cin>>n>>m;
         for(int i =1;i<=n;i++)
         {
             int a;cin>>a;
             S[i]=S[i-1]+a;
         }
         while(m--)
         {
             cout<<S[r]-S[l-1]<<endl;
         }
         return 0;
     }

    (很明显啊,该方法的空间复杂度较法一小,空间复杂度为O(n+m),大大提高了运算效率)

3.一维前缀和

  • 一维前缀和,顾名思义就是一维数组的前缀和,即一维数组中前n个元素的和。

  • 规律:

    • 一维数组an的某一前缀和:Sn=Sn-1+an

    • 一维数组an某一区间[l, r]的和:=S[r]-S[l-1]

  • 总结:

4.二维前缀和

  • 定义:

    给定一个二维数组a[] [],s[i] [j]表示二维数组中,左上角(1, 1)到右下角(i, j)所包围的矩阵元素的和(s[i] [j]所代表的值并不是从[1] [1]到[i] [j]的所有元素之和。)

  • 求二维数组某区间的前缀和;

    (x1, y1)为左上角,(x2, y2)为右下角的子矩阵的和为: s[x2, y2] - s[x1 - 1, y2] - s[x2, y1 - 1] + s[x1 - 1, y1 - 1]

  • 规律:

    • 二维数组某一前缀和:s[i, j]=s[i, j-1]+s[i-1, j]-s[i-1, j-1]+a[i,j]

    • 二维数组某一区间和:=s[x2, y2] - s[x1 - 1, y2] - s[x2, y1 - 1] + s[x1 - 1, y1 - 1]`

  • 例题:

    输入一个n行m列的整数矩阵,再输入q个询问,每个询问包含四个整数x1, y1, x2, y2,表示一个子矩阵的左上角坐标和右下角坐标。

    对于每个询问输出子矩阵中所有数的和。

    输入格式 第一行包含三个整数n,m,q。

    接下来n行,每行包含m个整数,表示整数矩阵。

    接下来q行,每行包含四个整数x1, y1, x2, y2,表示一组询问。

    输出格式

    共q行,每行输出一个询问的结果。

    思路:先创建一个数组用于储存二维数组an的数据;接着将数据输入到数组s[N] [N];再利用二维数组前缀和的公式s[i, j]=s[i, j-1]+s[i-1, j]-s[i-1, j-1]+a[i,j]对数组s进行遍历(对an数组进行遍历并将Sn的数据覆盖,是为了方便加上a[i, j],将原来数组s[N] [N]的数据覆盖,获得Sn数组。

    代码如下:

     #include <iostream>
     using namespace std;
     const int N = 1010;
     int n, m, q;
     int s[N][N];
     int main()
     {
         scanf("%d%d%d", &n, &m, &q);
         for (int i = 1; i <= n; i ++ )
             for (int j = 1; j <= m; j ++ )
                 scanf("%d", &s[i][j]);
         for (int i = 1; i <= n; i ++ )
             for (int j = 1; j <= m; j ++ )
                 s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
         while (q -- )
         {
             int x1, y1, x2, y2;
             scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
             printf("%d\n", s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]);
         }
         return 0;
     }
     ​

  • 总结:

    • 前缀和的运用一般用于区间求和

    • 前缀和的运用:先构造一个前缀和数列即Sn,再找出Sn和an区间和的关系,利用关系求区间和。

5.差分的定义

  • 差分可以看成前缀和的逆运算,即可以理解为给定数列Sn,求an数列,所求的an数列被称为Sn数列的差分数组

6.差分有何用

  • 可用于给一个给定数组某一区间的所有元素加上一个常数C

  • 给出一个问题:

    给定区间[l, r ],让我们把a数组中的[l, r]区间中的每一个数都加上c,即 a[l] + c , a[l + 1] + c , a[l + 2] + c ,,,,,, a[r] + c;

  • 法一:暴力解法

    暴力做法是for循环lr区间,时间复杂度O(n),如果我们需要对原数组执行m次这样的操作,时间复杂度就会变成O(n * m)。有没有更高效的做法吗? 考虑差分做法,(差分数组派上用场了)。

  • 法二:差分解法

    将a[n]看做是辅助数列b[n]的前缀和数列,即a[i]=b[1]+b[2]+,,,+b[i];

    所以对于将a[n]中区间[l,r]上每个数都加上常数c,我们先给b[l]+c,此时对应的a数组的区间[l,r]就变成了a[l]+c,a[l+1]+c,...,a[r]+c,a[r+1]+c、、、,

    为了避免从r以后的数列a的元素都加c,我们对b[r+1]-c,则a数组的区间[l,r]就变成了a[l]+c,a[l+1]+c,...,a[r]+c,a[r+1]+c-c,a[r+2]+c-c、、、,即a[l]+c,a[l+1]+c,...,a[r]+c,a[r+1],a[r+2],、、、

    因此,我们只要构造出b数组就可以对b数组中对应的元素进行加C从而影响a数组对应的值从而达到我们想要的目的。

7.一维差分

  • 定义:一维数组前缀和的逆运算

  • 构建一维差分数组:(已知数组s,求其差分数组a)

    • a[i] = s[i] - s[i-1]

  • 例题:

    题目练习: AcWing 797. 差分

    输入一个长度为n的整数序列。 接下来输入m个操作,每个操作包含三个整数l, r, c,表示将序列中[l, r]之间的每个数加上c。 请你输出进行完所有操作后的序列。

    输入格式 第一行包含两个整数n和m。 第二行包含n个整数,表示整数序列。 接下来m行,每行包含三个整数l,r,c,表示一个操作。

    输出格式 共一行,包含n个整数,表示最终序列。

    思路:先构建两个数组a,b(a为前缀和数组,b为差分数组),接着在输入a数组数据时,利用公式b[i] = a[i] - a[i - 1]进行差分数组的构建,然后对构建好的差分数组b进行数据处理,即在对应的数据b[l]+c,b[r+1]-c从而达到影响数组a的目的,最后对数组b进行前缀和运算。

    代码如下

     #include<iostream>
     using namespace std;
     const int N = 1e5 + 10;
     int a[N],b[N]; 
     int main()
     {
         int n,m;
         scanf("%d%d", &n, &m);
         for(int i = 1;i <= n; i++) 
         {
             scanf("%d", &a[i]);
             b[i] = a[i] - a[i - 1];      //构建差分数组
         }
         int l, r, c;
         while(m--)
         {
             scanf("%d%d%d", &l, &r, &c);
             b[l] += c;     //表示将序列中[l, r]之间的每个数加上c
             b[r + 1] -= c;
         }
         for(int i = 1;i <= n; i++) 
         {
             b[i] += b[i - 1];  //求前缀和运算
             printf("%d ",b[i]);
         }
         return 0;
     }
     ​

8.二维差分

  • 定义:二维差分实际就是二维前缀和的逆运算

  • 构造二维差分数组:

    由于二维不像一维那样好理解,所以我们先从如何对差分数组中元素处理,来影响二维数组a的值,从而使数组a某特定区间的元素都加C

  • 数据处理

    已知原数组a中被选中的子矩阵为 以(x1,y1)为左上角,以(x2,y2)为右下角所围成的矩形区域;即对该区间内a数组的每一元素都加c;

    假定我们已经构造好了b数组,类比一维差分,我们执行以下操作 来使被选中的子矩阵中的每个元素的值加上c

    b[x1][y1] + = c;

    b[x1,][y2+1] - = c;

    b[x2+1][y1] - = c;

    b[x2+1][y2+1] + = c;

    至于为什么要对差分数组b的数据进行这样的处理,就可以使得a数组对应的区间内的元素加c,课参考林大佬的图解 ,原文链接:前缀和与差分 图文并茂 超详细整理(全网最通俗易懂)_林小鹿@的博客-CSDN博客_前缀和差分

    接着我们可以对以上对数组b进行数据处理的操作封装为一个函数

     void insert(int x1,int y1,int x2,int y2,int c)
     {     //对b数组执行插入操作,等价于对a数组中的(x1,y1)到(x2,y2)之间的元素都加上了c
         b[x1][y1] += c;
         b[x2 + 1][y1] -= c;
         b[x1][y2 + 1] -= c;
         b[x2 + 1][y2 + 1] += c;
     }
     ​

    该函数的意思就是对b数组进行插入数据操作,从而影响数组a,对a数组中的(x1,y1)到(x2,y2)之间的元素都加上了c;就是说数组b插入操作,插入数为c,数组a某区间元素将都加c

    从以上角度出发,我们可以推出另一个结论,当我们需要给a数组某一个元素加C时同样可以对差分数组b插入操作。

    因此,我们可以假设数组a原来是空数组,此时对应的差分数组b也是空数组,对a数组逐个元素添加上a[i] [j]使得a数组变回原来题目给定的数组。

    在这个对a空数组每个空元素加a[i] [j]的过程,实际上就是对对应的b数组的数据进行插入操作,插入数c为a[i] [j]。因此,在a数组变回原来数组之际,其对应的差分数组b也跟着构建完成。

通过以上思路,我们明白了如何构建数组a的差分数组b,就是利用封装函数对差分数组b进行插入操作,插入数为a[i] [j]

  • 例题:

    题目练习: AcWing 798. 差分矩阵 输入一个n行m列的整数矩阵,再输入q个操作,每个操作包含五个整数x1, y1, x2, y2, c,其中(x1, y1)和(x2, y2)表示一个子矩阵的左上角坐标和右下角坐标。 每个操作都要将选中的子矩阵中的每个元素的值加上c。 请你将进行完所有操作后的矩阵输出。

    输入格式 第一行包含整数n, m, q。 接下来n行,每行包含m个整数,表示整数矩阵。 接下来q行,每行包含5个整数x1, y1, x2, y2, c,表示一个操作。

    输出格式 共 n 行,每行 m 个整数,表示所有操作进行完毕后的最终矩阵。

    代码如下

     #include<iostream>
     #include<cstdio>
     using namespace std;
     const int N = 1e3 + 10;
     int a[N][N], b[N][N];
     void insert(int x1, int y1, int x2, int y2, int c)
     {
         b[x1][y1] += c;
         b[x2 + 1][y1] -= c;
         b[x1][y2 + 1] -= c;
         b[x2 + 1][y2 + 1] += c;
     }
     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 >> a[i][j];
         for (int i = 1; i <= n; i++)
         {
             for (int j = 1; j <= m; j++)
             {
                 insert(i, j, i, j, a[i][j]);      //构建差分数组
             }
         }
         while (q--)
         {
             int x1, y1, x2, y2, c;
             cin >> x1 >> y1 >> x2 >> y2 >> c;
             insert(x1, y1, x2, y2, c);
         }
         for (int i = 1; i <= n; i++)
         {
             for (int j = 1; j <= m; j++)
             {
                 b[i][j] += b[i - 1][j] + b[i][j - 1] - b[i - 1][j - 1];  //二维前缀和
             }
         }
         for (int i = 1; i <= n; i++)
         {
             for (int j = 1; j <= m; j++)
             {
                 printf("%d ", b[i][j]);
             }
             printf("\n");
         }
         return 0;
     }
     ​
     ​

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值