【模板】二维前缀和与二维差分

前缀和是一种极其优秀的线性结构,也是一种重要的思想,能极大地降低区间查询的时间复杂度。

为了方便,涉及到前缀和的题目,通常使用全局数组(默认初始化为 0 0 0)且数组下标一般从 1 1 1 开始。

1.二维前缀和

问题描述:假设有一个 n × m n \times m n×m 大小的矩阵 a [   ] [   ] a[\ ][\ ] a[ ][ ],再给出 q q q 次询问,每次询问给出 x 1 ,   y 1 ,   x 2 ,   y 2 x_1,\ y_1,\ x_2,\ y_2 x1, y1, x2, y2 四个数,要求输出以 a [ x 1 ] [ y 1 ] a[x_1][y_1] a[x1][y1] 为左上角、 a [ x 2 ] [ y 2 ] a[x_2][y_2] a[x2][y2] 为右下角的子矩阵的所有元素和。

如果使用暴力解法,每次都遍历一遍给出的子矩阵,计算出答案,这样时间复杂度会达到 O ( n ∗ m ∗ q ) O(n*m*q) O(nmq),极有可能会 TLE。

如果使用二维前缀和来做的话,能将时间复杂度降到 O ( n ∗ m + q ) O(n*m+q) O(nm+q),极大地减少了时间。

1.1 求解二维前缀和数组

二维前缀和数组 s u m [ i ] [ j ] sum[i][j] sum[i][j] 就是原数组中以 a [ 1 ] [ 1 ] a[1][1] a[1][1] 为左上角、 a [ i ] [ j ] a[i][j] a[i][j] 为右下角的子矩阵的元素和。

以下图中的 a [ 3 ] [ 3 ] = 13 a[3][3]=13 a[3][3]=13 为例,其前缀和 s u m [ 3 ] [ 3 ] sum[3][3] sum[3][3] 就是黑框框起来的子矩阵元素之和,即以 a [ 1 ] [ 1 ] a[1][1] a[1][1] 为左上角、 a [ 3 ] [ 3 ] a[3][3] a[3][3] 为右下角的子矩阵的元素和。

在这里插入图片描述

从递推的角度来看, s u m [ 3 ] [ 3 ] = s u m [ 3 ] [ 2 ] + s u m [ 2 ] [ 3 ] − s u m [ 2 ] [ 2 ] + a [ 3 ] [ 3 ] sum[3][3]=sum[3][2]+sum[2][3]-sum[2][2]+a[3][3] sum[3][3]=sum[3][2]+sum[2][3]sum[2][2]+a[3][3]

故有: s u m [ i ] [ j ] = s u m [ i ] [ j − 1 ] + s u m [ i − 1 ] [ j ] − s u m [ i − 1 ] [ j − 1 ] + a [ i ] [ j ] sum[i][j]=sum[i][j-1]+sum[i-1][j]-sum[i-1][j-1]+a[i][j] sum[i][j]=sum[i][j1]+sum[i1][j]sum[i1][j1]+a[i][j]

// 求以a[1][1]为左上角、a[i][j]为右下角的子矩阵中的元素和
void init()
{
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            sum[i][j] = sum[i][j - 1] + sum[i - 1][j] - sum[i - 1][j - 1] + a[i][j];
        }
    }
}

1.2 区间查询

每次要查询的答案就是下图中的黄色部分。

在这里插入图片描述

当给出 x 1 ,   y 1 ,   x 2 ,   y 2 x_1,\ y_1,\ x_2,\ y_2 x1, y1, x2, y2 时,要查询的值即为 s u m [ x 2 ] [ y 2 ] − s u m [ x 2 ] [ y 1 − 1 ] − s u m [ x 1 − 1 ] [ y 2 ] + s u m [ x 1 − 1 ] [ y 1 − 1 ] sum[x_2][y_2]-sum[x_2][y_1-1]-sum[x_1-1][y_2]+sum[x_1-1][y_1-1] sum[x2][y2]sum[x2][y11]sum[x11][y2]+sum[x11][y11]

int get(int x1, int y1, int x2, int y2)
{
    return sum[x2][y2] - sum[x2][y1 - 1] - sum[x1 - 1][y2] + sum[x1 - 1][y1 - 1];
}

2.二维差分

2.1 定义

假设有原数组 a [   ] [   ] a[\ ][\ ] a[ ][ ],现构造出一个数组 b [   ] [   ] b[\ ][\ ] b[ ][ ],使得 a [ i ] [ j ] a[i][j] a[i][j] 等于 b [ i ] [ j ] b[i][j] b[i][j] 及其左上所有元素的和,那么 b [   ] [   ] b[\ ][\ ] b[ ][ ] 就称为 a [   ] [   ] a[\ ][\ ] a[ ][ ] 的差分, a [   ] [   ] a[\ ][\ ] a[ ][ ] 就称为 b [   ] [   ] b[\ ][\ ] b[ ][ ] 的前缀和。

可以发现,差分与前缀和是逆运算。

2.2 区间修改

由上述定义可知,差分数组 b [ i ] [ j ] b[i][j] b[i][j] 的前缀和就是原数组 a [ i ] [ j ] a[i][j] a[i][j] 的值。

利用差分数组 b [   ] [   ] b[\ ][\ ] b[ ][ ] 可以快速地对原数组 a [   ] [   ] a[\ ][\ ] a[ ][ ] 进行区间修改,时间复杂度为 O ( 1 ) O(1) O(1)

假如现在要将原数组 a [   ] [   ] a[\ ][\ ] a[ ][ ] 的以 a [ x 1 ] [ y 1 ] a[x_1][y_1] a[x1][y1] 为左上角、 a [ x 2 ] [ y 2 ] a[x_2][y_2] a[x2][y2] 为右下角的矩形区域里的每个数都加上 x x x,代码如下:

// 以a[x1][y1]为左上角、a[x2][y2]为右下角的子矩阵中的所有元素加上x
void add(int x1, int y1, int x2, int y2, int x)
{
    b[x1][y1] += x;
    b[x2 + 1][y1] -= x;
    b[x1][y2 + 1] -= x;
    b[x2 + 1][y2 + 1] += x;
}

2.3 初始化

问题:二维差分数组 b [   ] [   ] b[\ ][\ ] b[ ][ ] 是如何构造出来的呢?

二维差分数组 b [ i ] [ j ] b[i][j] b[i][j] 可用如下公式计算:

b [ i ] [ j ] = a [ i ] [ j ] − a [ i ] [ j − 1 ] − a [ i − 1 ] [ j ] + a [ i − 1 ] [ j − 1 ] b[i][j] = a[i][j] - a[i][j-1] - a[i-1][j] + a[i-1][j-1] b[i][j]=a[i][j]a[i][j1]a[i1][j]+a[i1][j1]

事实上,我们不需要过分关注差分数组 b [   ] [   ] b[\ ][\ ] b[ ][ ] 是怎么构造出来的,只需要知道差分与前缀和是互逆运算即可。

一开始,可以把原数组 a [   ] [   ] a[\ ][\ ] a[ ][ ] 想象成全是 0 0 0,此时相应的差分数组 b [   ] [   ] b[\ ][\ ] b[ ][ ] 也全是 0 0 0

接下来,对原数组 a [   ] [   ] a[\ ][\ ] a[ ][ ] 的初始值可以做如下考虑:

  • a [ 1 ] [ 1 ] a[1][1] a[1][1] 相当于以 a [ 1 ] [ 1 ] a[1][1] a[1][1] 为左上角、 a [ 1 ] [ 1 ] a[1][1] a[1][1] 为右下角的矩形区域里的每个数都加上 a [ 1 ] [ 1 ] a[1][1] a[1][1],即 add(1, 1, 1, 1, a[1][1])

  • a [ 1 ] [ 2 ] a[1][2] a[1][2] 相当于以 a [ 1 ] [ 2 ] a[1][2] a[1][2] 为左上角、 a [ 1 ] [ 2 ] a[1][2] a[1][2] 为右下角的矩形区域里的每个数都加上 a [ 1 ] [ 2 ] a[1][2] a[1][2],即 add(1, 2, 1, 2, a[1][2])

  • a [ n ] [ m ] a[n][m] a[n][m] 相当于以 a [ n ] [ m ] a[n][m] a[n][m] 为左上角、 a [ n ] [ m ] a[n][m] a[n][m] 为右下角的矩形区域里的每个数都加上 a [ n ] [ m ] a[n][m] a[n][m],即 add(n, m, n, m, a[n][m])

这样,利用区间修改操作 a d d ( ) add() add() 即可完成赋初始值,从而避免了手动构造差分数组 b [   ] [   ] b[\ ][\ ] b[ ][ ]

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值