差分及模板

差分及模板

1.差分的定义及问题引出

    给定一个数组:A = a1,a2,...,an
    现在构造一个数组B = b1,b2,...,bn,使得:
        ai = b1+b2+...+bi
    那么B就是A的差分。
    根据前缀和的定义,我们发现:差分就是前缀和的逆运算。
    根据上述定义,引出两个问题:
        1.  如何构造差分B?
        2.  差分的作用?

2. 一维差分的构造及作用

    我们可以根据如下公式,来进行构造差分B:
        b1 = a1
        b2 = a2 - a1
        b3 = a3 - a2
        ...
        bn = an - an-1
    但是,我们不打算以这种方式来构造差分数组B,我们在之后进行讲解。
    那么,差分的作用是什么呢?
        1.假设,已经构造出来了差分数组B,那么如果想求原数组A,那么只需要对差分数组B求前缀和即可。换句话说,只要有差分数组B,那么我们就可以以O(n)的时间复杂度来求出原数组A。
        2.有这样一个应用场景:现在我们想让原数组A当中[l,r]区间内的所有元素全部加上一个常数C(或其余类似操作)。如果用暴力来做的话,就是O(n)。但是,如果我们对差分数组B操作的话,时间复杂度就是O(1)。具体如下:
    1.  首先让bl+C,bl+C的作用就是:原数组al之后(包括al)的所有元素都加上了常数C。
    2.  再让br+1-C,br+1-C的作用就是:原数组ar+1之后(包括ar+1)的所有元素都减去了C。
    3.  经过这两个步骤之后,我们就会发现:al之后的所有元素加上了C,而ar+1之后的元素都减去了C。这样的话就保证了原数组al-ar之间的所有元素都加上了C,而原数组其余元素保持不变。
    因此,原来对原数组O(n)的操作,在差分数组B上操作就变成了O(1)。
    完成上述操作之后,再对修改后的差分数组求前缀和之后,就可以得到满足条件的A数组了。
    如何构造差分数组B?
        假设我们已经拥有了原数组A,那么如何构造差分数组B?
        1.  我们可以假定:原数组A都为0,那么A对应的差分数组B也都为0。
        2.  但是,原数组A已经给出。那么,原数组A可以看作都为0的差分数组B进行n次插入操作后,再通过对差分数组B进行前缀和而得出来的数组。换句话说,想要得到原数组A,就需要得到A对应的差分数组B。差分数组B实际上就是先初始化为0,再进行n次插入操作而得到。那么插入操作具体如何?
        3.  对初始化为0的差分数组B进行n次插入操作实际上如下:
            1.  对[1,1]区间加上a1。实际上为:b[1] += a[1] b[1+1] -= a[1]。这样的效果就是:a1之后的元素包括a1,都加上了a[1],a2之后的元素包括a2,都减去了a[1]。若如此做原数组只初始化了a1 = a[1]。
            2.  对[2,2]区间加上a2。操作如上。
            3.  ...
            4.  对[n,n]区间加上an。
    注意:前缀和和差分的问题下标都从1开始,方便编程。

3. 一维差分的模板

给区间[l, r]中的每个数加上c:B[l] += c, B[r + 1] -= c

4. 一维差分的例题

https://www.acwing.com/problem/content/799/
#include <iostream>
#include <cstdio>

using namespace std;

int a[100010],b[100010];        //代表原数组和差分数组

//代表对差分数组B进行插入操作
void insert(int l,int r,int c){
    b[l] += c;
    b[r+1] -= c;
}

int main(){
    int n,m;
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
    }
    //构造A对应的差分数组B,B经过前缀和之后,可转化为A。
    for(int i=1;i<=n;i++){
        insert(i,i,a[i]);
    }
    int l,r,c;
    while(m--){
        scanf("%d %d %d",&l,&r,&c);
        insert(l,r,c);
    }
    //将差分B通过前缀和转换为对应的数组A
    for(int i=1;i<=n;i++){
        b[i] += b[i-1];
    }
    //输出原数组A
    for(int i=1;i<=n;i++){
        printf("%d ",b[i]);
    }
    return 0;
}

5. 二维差分的定义及问题引出

    二维差分和一维是类似的。假设我们有原矩阵A,A中i行j列的元素为aij,那么差分矩阵的定义就是:存在一个矩阵B,B中i行j列的元素为bij,使得aij为bij的左上角元素之和。即:
    aij = b11 + b12 + ... + b1j + ... + bi1 + bi2 + ... + bij。
    根据上述定义,引出两个问题:
        1.  如何构造差分矩阵B?
        2.  差分的作用?

6. 二维差分的构造及作用

    那么,二维差分的作用是什么呢?假设有这样一个场景:我们需要给原矩阵中的子矩阵加上一个常数C,那么如果在原矩阵中直接操作,那么就是O(n*m)。如果在差分矩阵中进行操作,时间复杂度就是O(1)。再通过求差分矩阵的前缀和之后,就得到了修改后的新矩阵。
    那么,如何完成上述的插入操作呢?
        假设,我们需要给(x1,y1)和(x2,y2)所围成的子矩阵加上常数C,操作如下:
            1.  先让bx1y1 += C,根据二维差分的定义,这样做的效果,就是在原矩阵A中ax1y1左下角的所有元素都加上了C。
            2.  再让bx2+1,y1 -= C。
            3.  再让bx1,y2+1 -= C。
            4.  再让bx2+1,y2+1 += C。
        经过上述四个步骤后,再对二维差分矩阵求前缀和,就得到了更新之后的新数组A.
    二维差分也是二维前缀和的逆运算。关于如何构造差分矩阵B,跟构造差分数组是类似的。假设原矩阵A中的每个元素均为0,那么其差分矩阵自然为0。然后,遍历差分矩阵中的每一个元素作n*m次插入操作即可。具体如下:(跟差分数组的初始化是类似的)
        1.  在插入的时候,可以将差分矩阵中的每一个元素看成一个子矩阵进行插入。
        2.  插入的值就是原矩阵中的元素aij。
        3.  确定好范围和值后,再根据上述的四个操作进行n*m次插入即可。
        4.  这样的话,就完成了差分矩阵的初始化,如若对差分矩阵求前缀和,那么得到的就是原矩阵A。

7. 二维差分模板

给以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵中的所有元素加上c:
S[x1, y1] += c, S[x2 + 1, y1] -= c, S[x1, y2 + 1] -= c, S[x2 + 1, y2 + 1] += c

8. 二维差分例题

https://www.acwing.com/problem/content/800/
#include <iostream>
#include <cstdio>

using namespace std;

int a[1010][1010];
int b[1010][1010];

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;
    scanf("%d %d %d",&n,&m,&q);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            scanf("%d",&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;
        scanf("%d %d %d %d %d",&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;
}
    作者:gao79138
    链接:https://www.acwing.com/
    来源:本博客中的截图、代码模板及题目地址均来自于Acwing。其余内容均为作者原创。
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
posted @   夏目^_^  阅读(53)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示