zoukankan      html  css  js  c++  java
  • 一维差分和二维差分

    1、什么是一维差分?

    一维差分是一维前缀和的逆运算,举个栗子:

    原数组与差分数组的关系:原数组前后两项的差就是差分数组的值:(b[i]=a[i]-a[i-1])
    对于第1个差分数组的值,(b[1]=a[1]-a[0]),因为(a[0])初始化为(0),所以(b[1]=a[1]),但也不妨碍我们按通用的方式进行理解。

    差分数组与原数组的关系:差分数组的前缀和是原数组:(a[i]=b[1]+b[2]+b[3]+...+b[i])

    2、一维差分的构建

    void insert(int l,int r,int c){
        b[l]+=c;
        b[r+1]-=c;
    }
    

    上面就是构建的方法,我们来理解一下:
    (a_1=9,a_2=3,a_3=5,a_4=4,a_5=2)这个原始数组为例,根据上面的计算办法构建差分数组:

    第一步:insert(1,1,9),就是l=1,r=1,c=9,那么b[1]=9,b[2]=-9
    第二步:insert(2,2,3),就是l=2,r=2,c=3,那么b[2]=-9+3=-6,b[3]=0-3=-3
    第三步:insert(3,3,5),就是l=3,r=3,c=5,那么b[3]=-3+5=2,b[4]=0-5=-5
    第四步:insert(4,4,4),就是l=4,r=4,c=4,那么b[4]=-5+4=-1,b[2]=0-4=-4
    第五步:insert(5,5,2),就是l=5,r=5,c=2,那么b[5]=-4+2=-2,b[2]=0-2=-2

    3、一维差分用途

    当我们要在某个区间([l,r])的所有值都加上一个数(x)时:我们只需要在差分数组中进行一加一减即可:(b[l]+c,b[r+1]−c)

    为什么呢?
    (a[1]=9,a[2]=3,a[3]=5,a[4]=4,a[5]=2)这个原始数组为例,现在要地(a_2至a_4)这个范围内增加(2):
    (a[1]=b[1])
    (a[2]=b[1]+b[2])
    (a[3]=b[1]+b[2]+b[3])
    (a[4]=b[1]+b[2]+b[3]+b[4])
    (a[5]=b[1]+b[2]+b[3]+b[4]+b[5])
    (a[6]=b[1]+b[2]+b[3]+b[4]+b[5]+b[6])

    现在要在(a_2至a_4)增加数字2,我们看看能不能通过增大(b)数组某两个值的方法对它们进行维护。
    比如(b[2]+=2),那么受影响的是(a[2])及后面所有的(a)数组元素,包括(a[5],a[6]),这样还不行,因为我们只要求变化到(a[4]),看来还需要再做点什么,比如从(b[5])中再减去数字2,这时我们惊喜的发现,从 (a[5])开始,加2带来的负作用解除了!

    3、差分数组与原数组一样长吗?为什么我看到了 b[r+1]=c,这样差分不就多了一个元素吗?

    QQ截图20210213191841.png

    不是一样长的,差分数组其实是比原数组多一个元素的,多的那个在数组的最后面。
    当然,一般我们是不用到最后面的那个元素的,因为,差分的概念都是描述当前位置和前一个位置的差,前缀和就是原数组了,最后一个生成的元素可以视为无效元素,没有必要再纠结了。

    4、C++ 代码

    #include<iostream>
    using namespace std;
    const int N=100010;
    int n,m;
    int a[N],b[N];
    void insert(int l,int r,int c){
        b[l]+=c;
        b[r+1]-=c;
    }
    int main(){
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)scanf("%d",&a[i]),insert(i,i,a[i]);
        while(m--){
            int l,r,c;
            scanf("%d%d%d",&l,&r,&c);
            insert(l,r,c);
        }
        for(int i=1;i<=n;i++)b[i]+=b[i-1],printf("%d ",b[i]);
        
        return 0;
    }
    

    5、二维差分概念

    差分是前缀和的逆运算。就是如果有一个二维的数组b,那么b[1][1]+....+b[i][j],就是一个矩形范围,它的最终加法结果值应该就是a[i][j].就是原数组i,j的位置值。

    6、二维差分有什么用?

    和一维差分的用途基本一致,在一个二维矩阵中,有多块区间需要增加或减少一个数值,多次操作后求最终的矩阵内容。如果按照传统办法,就是二层循环,复杂度很高,如果预处理出一个二维的差分矩阵,以后的多轮操作都转为了4次加加减减操作,可以视为(O(1))级别的时间复杂度,运算效率将得到极大提高。

    7、看一下实际的例子

    #include<iostream>
    
    using namespace std;
    const int N = 1010;
    
    int a[N][N], b[N][N];
    
    /**
    测试用例:
    
    原始数组
    1   2   3   4
    5   6   7   8
    9   10  11  12
    13  14  15  16
    
    二维差分数组
    1 1 1 1 
    4 0 0 0  
    4 0 0 0  
    4 0 0 0 
     */
    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() {
        for (int i = 1; i <= 4; i++)
            for (int j = 1; j <= 4; j++)
                scanf("%d", &a[i][j]);
    
        for (int i = 1; i <= 4; i++)
            for (int j = 1; j <= 4; j++)
                insert(i, j, i, j, a[i][j]);
    
        for (int i = 1; i <= 4; i++) {
            for (int j = 1; j <= 4; j++) {
                printf("%d ", b[i][j]);
            }
            printf("
    ");
        }
    
        return 0;
    }
    
    

    8、二维差分公式推导

    (① s[3][3] = b[1][1]+b[1][2]+b[1][3])
    ( + b[2][1]+b[2][2]+b[2][3])
    ( + b[3][1]+b[3][2]+b[3][3])

    (② s[2][3] = b[1][1]+b[1][2]+b[1][3])
    ( + b[2][1]+b[2][2]+b[2][3])

    (③ s[3][2] = b[1][1]+b[1][2])
    ( + b[2][1]+b[2][2])
    ( + b[3][1]+b[3][2])

    (④ s[2][2] = b[1][1]+b[1][2])
    ( + b[2][1]+b[2][2])

    (①-②-③+④)
    (b[3][3]=s[3][3]-s[2][3]-s[3][2]+s[2][2])

    抽象一下,就可以获取到从原矩阵获取差分矩阵的公式
    (b[i][j]=s[i][j]-s[i-1][j]-s[i][j-1]+s[i-1][j-1])

    如何从差分矩阵得到原矩阵呢?可以参考下面公式(就是二维前缀和)
    (s[i][j]=b[i][j]+b[i-1][j]+b[i][j-1]-b[i-1][j-1])

    9、二维差分如何构建?

    如果我们要在左上角是 (x1,y1),右下角是 (x2,y2) 的矩形区间每个值都 +c,如下图所示
    1.png
    在我们要的区间开始位置(x1,y1)+c,根据前缀和的性质,那么它影响的就是整个黄色部分,多影响了两个蓝色部分,所以在两个蓝色部分 -c 消除 +c 的影响,而两个蓝色部分重叠的绿色部分多了个 -c 的影响,所以绿色部分 +c 消除影响。所以对应的计算方法如下:

    void insert(int x1, int y1, int x2, int y2, int c){
        b[x1][y1] += c;             //开始位置增加C
        b[x2 + 1][y1] -= c;         //见上面的图,把第二行的蓝色区域减去C
        b[x1][y2 + 1] -= c;         //见上面的图,把第一行的蓝色区域减去C
        b[x2 + 1][y2 + 1] += c;     //交叉位置被减了两次,需要补回来。
    }
    

    10、C++ 代码

    #include<iostream>
    using namespace std;
    const int N=1010;
    
    int n,m,q;
    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(){
        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]);
            puts("");
        }
        return 0;
    }
    
  • 相关阅读:
    委托和事件的区别和联系(转载
    Captcha验证码识别走向3D化
    3DCaptcha for .net
    委托之异步(转自http://www.cnblogs.com/inforasc/archive/2009/10/21/1587756.html
    static的初始化顺序
    各式各样的验证码
    [SQL优化工具]SQL Tuning for SQL Server(转)
    浅析C#深拷贝与浅拷贝 (转载)
    [orcle] oracle截取字符串的函数substr
    struts2.0的工作原理
  • 原文地址:https://www.cnblogs.com/littlehb/p/14957794.html
Copyright © 2011-2022 走看看