Acwing算法基础【1】基础(三)前缀和与差分

目录

三、前缀和与差分

3.1 前缀和

3.1.1 一维前缀和的算法思想

3.1.2 一维前缀和的代码实现

3.1.3 二维前缀和的基本思想

3.1.4 二维前缀和的代码实现

3.2 差分

3.2.1 基本思想

3.2.2 代码实现

3.2.3 二维差分的基本思想

3.2.4 二维差分的代码实现


三、前缀和与差分

        前缀和与差分是一堆逆运算

3.1 前缀和

3.1.1 一维前缀和的算法思想

        原数组:一个长度为n的数组{a1, a2, a3 … , an}

        前缀和数组:Si = a1+a2+…+ai,S0=0

                (第一个下标一定要是1,把S0定义成0,这两个都是为了方便处理边界)

   1、前缀和的作用:

        能够快速地求出来原数组中一段数据的和,例如,如果想算一个数组中[l,r]的 数组的和,没有前缀和数组的话,复杂度就是O(n)的 ,如果有的话,我们就是可以直接 用 Sr - Sl-1 得到,查询的复杂度就是O(1), 但是求一个前缀和数组的复杂度就是O(n),适用于输入一组数据,让求好几段的数据的和的情况,

   2、如何求Si?

        ( 可以用for循环 递推遍历一遍,S[i] = S[i-1]+ai)

        

3.1.2 一维前缀和的代码实现

#include <iostream>
using namespace std;

const int N = 100010;

int q[N];
int s[N];

int main(){
    
    int n, m;
    scanf("%d %d", &n, &m);
    
    for(int i = 0; i < n; i++){
        scanf("%d", &q[i]);
    }
    
    s[0] = 0;
    
    // 构造前缀和数组
    for(int i = 1; i <= n;i++){
        s[i] = s[i-1] + q[i-1];
    }
    
    int l, r;
    while(m--){
        scanf("%d %d", &l, &r);
        printf("%d\n", s[r]-s[l-1]); //区间相减
    }
    
    return 0;
}

3.1.3 二维前缀和的基本思想

        二位前缀和,画个图 如下所示:

     1、如何求某区间的和?

         

        想要求x1, x2, x3, x4所围成的小矩形 的和,就可以用,Sx2y2 - Sx1y2 - Sx2y1 + Sx1y1

    2、如何求一个Si j

         可以用两层for循环进行遍历,S[i][j] = S[i-1][j] +S[i][j-1] -S[i-1][j-1]+aij

        所以定义S0=0,然后第一数都从1开始,就可以避免边界问题

3.1.4 二维前缀和的代码实现

#include <iostream>
using namespace std;

const int N= 1010;

int q[N][N];

int main(){
    int n, m, qn;
    
    scanf("%d %d %d", &n, &m, &qn);
    
    //一定要从1开始,不然边界问题好难处理
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= m; j++){
            scanf("%d", &q[i][j]);
        }
    }
    
    int s[N][N];
    
    // 构建前缀和
    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]+q[i][j]; 
        }
    }
    
    int xa, ya, xb, yb;
    
    //区间更新
    while(qn--){
        scanf("%d %d %d %d", &xa, &ya, &xb, &yb);
        printf("%d\n", s[xb][yb] - s[xa-1][yb] -s[xb][ya-1] +s[xa-1][ya-1]);
    }
    
    return 0;
}

3.2 差分

        差分是前缀和的逆运算

3.2.1 基本思想

        数组:一个长度为n的数组{a1, a2, a3 … , an}

        构造 一个b 数组,{b1,b2,b3…bn}

                使得 ai = b1+b2+… +bi

                即 b1 = a1; b2= a2-a1; b3 = a3-a2, … , bn = an-an-1

                b 就是a 的差分,a就是b 的前缀和

   1、差分数组的作用:

        也就是在O(n)的时间, 通过 b 差分数组 可以还原出他的 前缀和数组 a ,运用 差分的思想 可以很好的解决一类问题

        例如给定一个区间 [l, r]  让a数组,这里边所有的数都加上c,如果是用遍历,那就需要O(n)的复杂度

        如果用差分来做,就可以做到O(1)原理如下:

      因为 ai = b1+ b2 + …+ bi  

      所以只需要对 b[l] 做 +c 的操作,那 a[l] 往后的数都会 +c

      又因为题目要求,只是 [l, r] 的区间,所以在对b[r+1] 做-c 的操作,相抵消,那 a[r+1] 之后的数就不会加上c了,

      这样再用b数组求前缀和,还原出的a数组 就是最后的结果

        所以只需要O(1)的时间就可以实现,以空间换时间

  2、如何拥有差分数组呢(初始化)

        其实不要考虑 差分数组 的构造,换个角度考虑从0开始用bn 生成an,这样an生成之后,b数组出来了,具体思路如下:

        可以先想象a 全部都是0,那么b  也全部都是

        然后怎么样变成{a1, a2, a3 … , an}呢

        就是相当于 每个地方,进行了加上这个数的操作

        就[1,1] 的区间+a1, [2, 2]的区间+a2 …[n, n]的区间+an

        这样生成 an  和 完成题目的an 某区间+c 操作,就都可以用同样的代码段了。

3.2.2 代码实现

#include<iostream>

using namespace std;

const int N = 100010;

int a[N],b[N];

//在某区间插入某个数,以得到差分数组和前缀和数组
int insert( int l, int r, int c){
    b[l] = b[l]+c;
    b[r+1] = b[r+1] -c;
}

int main(){
    
    int n, m;
    scanf("%d %d", &n, &m);
    int c;
    //假设本身数组a全是0, 那其差分数组也是全0
    //然后一个个给两个数组插入数据,例如在[1,1] 加上a1,调用insert函数
    //注意边界,最好从1开始,不然边界超级难处理
    for(int i = 1; i <= n; i++){
        scanf("%d", &c);
        a[i] = c;
        insert( i, i, c); //意思就是从i 到i这个区间上的数字 都加上c,
    }
    int l, r;
    //更新区间
    while(m--){
        scanf("%d %d %d", &l, &r, &c);
        insert( l, r, c);
    }
    
    
    //用更新后的b数组还原a数组
    for(int i = 1; i <= n; i++){
        a[i] = a[i-1] + b[i]; 
    }
    
    //输出最终的数组
    for(int i = 1; i <= n; i++){
        printf("%d ", a[i]);
    }
    
    
    return 0;
}

3.2.3 二维差分的基本思想

        思路和一维的是一样的,也不需要考虑直接构造差分数组,而是从0 进行初始化,用bn来生成an,这样b 和 a 数组就都是初始的状态,我们只需要考虑,如何更新:

    1、如何更新二维差分数组

        

         如图,在b数组对应的点进行+c 或者 -c的操作,会影响的整个a数组这个点往右及往下的区域都+c或者-c,所以如果只需要对a数组的 x1 x2 y1 y2 围成的矩形中进行+c操作,转化为对b点的操作就是

        b[x1][y1] + c;    b[x2][y1] - c;    b[x1][y2]-c;    b[x2][y2]+c

3.2.4 二维差分的代码实现

        

#include <iostream>

using namespace std;

const int N = 1010; //二维数组的时候,这里不要太大,满足要求就行

int q[N][N], b[N][N];

void insert(int xa, int ya, int xb, int yb, int c){
    b[xa][ya] += c;
    b[xa][yb+1] -= c;
    b[xb+1][ya] -=c;
    b[xb+1][yb+1] +=c;
}


int main(){
    
    int n, m, qn;
    
    scanf("%d %d %d", &n, &m, &qn);
    
    //以0开始构建数组
    for(int i = 1; i<=n; i++){
        for(int j = 1; j<=m; j++){
            scanf("%d", &q[i][j]);
            insert(i,j,i,j,q[i][j]);
        }
    }
    
    int xa, ya, xb, yb, c;
    //更新区间
    while(qn--){
        scanf("%d %d %d %d %d", &xa, &ya, &xb, &yb, &c);
        insert( xa, ya, xb, yb, c);
    }
    
    //生成最后的q数组
    for(int i = 1; i<=n; i++){
        for(int j = 1; j<=m; j++){
            q[i][j] = q[i][j-1]+q[i-1][j]-q[i-1][j-1]+b[i][j];
        }
    }

    //输出最终结果
    for(int i = 1; i<=n; i++){
        for(int j = 1; j<=m; j++){
            printf("%d ", q[i][j]);
        }
        printf("\n");
    }
    
    
    return 0;
}

写在最后:

这小节所讲的前缀和与差分,更重要的是一种思想,要明白各自适用于解决什么样的问题,可以在后续做题过程中多总结。加油

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值