一维二维差分

244 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第25天,点击查看活动详情

差分

一维差分

差分可以看成前缀和的逆运算

image-20221029172019251

B:差分数组, A:前缀和数组 A[i] = B[0] + B[1] +... + B[i] B[i] = A[i] - A[i - 1] ,每一个a[i]都是b数组中从头开始的一段区间和

  • 给A数组区间[L,R]中的每个数加上常数C:B[L] += C, B[R + 1] -= C

image-20221031102824827


如何构造差分数组呢,最直接的方法:

image-20221029195610863

b[i] = a[i] - a[i - 1];      //构建差分数组

我们只要有b数组,通过前缀和运算,就可以在O(n) 的时间内得到a数组


如何得到给A数组区间[L,R]中的每个数加上常数C后的数组呢?

根据差分数组 O(N)的时间得到该数组: a[i] = b[i] + a[i - 1];


例子:

image-20221029195832180

//差分 时间复杂度 o(m)
#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--) //进行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] = a[i] - a[i - 1] 
        //所以前缀和的a[i]:  b[i] + a[i - 1]
        a[i] = b[i] + a[i - 1];    //前缀和运算
        printf("%d ", a[i]);
    }
    return 0;
}

构造差分数组的方式2:可以认为原来A数组都是全0的,但是并不是全0,然后相当于在[i,i]区间加上a[i]这个值 .... 进行n次插入操作

for (int i = 1; i <= n; i ) insert(i, i, a[i]);

  • 其实是假定a数组最开始都是0,那么b数组初始时就是a数组的差分数组了
  • 对于每一个a[i],相当于插入了一个数,可以直接调用insert函数即可
#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) //在[l,r]区间插入c
{
    b[l] += c;
    b[r + 1] -= c;
}
​
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ ) 
    {
        cin >> a[i];
        insert(i, i, a[i]);//构造差分数组
    }
​
    while (m -- ) //进行m次操作
    {
        int l, r, c;
        cin >> l >> r >>c;
        insert(l, r, c);
    }
​
    for (int i = 1; i <= n; i ++ ) 
    {
        a[i] = a[i-1]+b[i];//b[i] = a[i] - a[i-1]
        printf("%d ", a[i]);
    }
    return 0;
}

二维差分

a[][]数组是b[][]数组的前缀和数组,那么b[][]a[][]的差分数组,构造差分数组: b[i][j],使得a数组中a[i][j]b数组左上角(1,1)到右下角(i,j)所包围矩形元素的和

如何构造差分数组

我们使用差分操作在对原数组进行修改的过程中,实际上就可以构造出差分数组,同一维差分,我们构造二维差分数组目的是为了 让原二维数组a中所选中子矩阵中的每一个元素加上c的操作,可以由O(n*n)的时间复杂度优化成O(1)

假设原数组a中被选中的子矩阵为 以(x1,y1)为左上角,以(x2,y2)为右下角所围成的矩形区域,想让这个区域的每个数都加上常数C

  • a数组是b数组的前缀和数组,对b数组的b[i][j]的修改,会影响到a数组中从a[i][j]及往后的每一个数

image-20221031104947384

b[i][j]加C : a[i][j]及其右下角的值都加上C


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;
}

我们可以先假想a数组为空,那么b数组一开始也为空,但是实际上a数组并不为空,因此我们每次让以(i,j)为左上角到以(i,j)为右下角面积内元素(其实就是一个小方格的面积)去插入 c = a[i][j],等价于原数组a(i,j)(i,j)范围内 加上了 a[i][j] ,因此执行n*m次插入操作,就成功构建了差分b数组

for(int i = 1;i <= n;i++)
{
    for(int j = 1;j <= m;j++)
    {
        insert(i, j, i, j, a[i][j]);    //构建差分数组
    }
}

关于二维差分操作也有直接的构造方法

b[i][j] = a[i][j]a[i − 1][j]a[i][j − 1] + a[i −1][j − 1]

image-20221031110304524


image-20221031164729322

#include<iostream>
using namespace std;
const int N = 1010;
int a[N][N], b[N][N];   
//给(x1,y1)到(x2,y2)围成的区域每个数加上c
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];
            insert(i, j, i, j, a[i][j]);      //构建二维差分数组
       }
    }
            
    while (q--) //执行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] = a[i][j] − a[i − 1][j] − a[i][j − 1] + a[i −1][j − 1]
            //所以二维前缀和数组的a[i][j]:
            a[i][j] = b[i][j] + a[i][j - 1] + a[i - 1][j] - a[i - 1][j - 1];
            printf("%d ", a[i][j]);
        }
        printf("\n");
    }
    return 0;
}