前缀和与差分

一维前缀和

前缀和一般用于预处理一个数组,然后在O(1)时间内获取该数组[l, r]区间内的和。对于数组a,有它的前缀和数组b,此时b[i] = b[i - 1] + a[i]

二维前缀和

拓展到二维,假设我们有数组a的前缀和数组b,那么我们可以在O(1)时间内求出数组a中某一个矩阵的和,该矩阵左上角为[x1, y1],右下角为[x2, y2]。

求矩阵和:b[x2][y2] - b[x1 - 1][y2] - b[x2][y1 - 1] + b[x1 - 1][y1 - 1]

求数组b:b[i][j] = b[i - 1][j] + b[i][j - 1] - b[i - 1][j - 1] + a[i][j]

差分

假设有数组a,我们一定可以求出一个数组b,该数组b的前缀和数组等于a。因为对于任意一个数组c,都可以求出c的前缀和,那么就可以反推。我们把数组a叫做数组b的前缀和数组,数组b叫数组a的差分数组。

假设题目,对于数组a,使数组a的[l, r]区间内的值加c,并重复该操作n次,最后求数组a是多少。对于一次的区间加数,可以直接遍历完成,但是n次的区间加数,每次都遍历一遍,复杂度为O(n * (r - l + 1))

假设我们有差分数组b(怎么得到差分数组一会再讨论),使得b[l] + c,那么b的前缀和数组,l后边的数都会加c;使得b[r + 1] - c,那么b的前缀和数组,r + 1后边的数都会减c。综合两种操作,可以使b的前缀和数组中[l, r]区间内的值加c。

我们称该操作为insert操作:

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

所以只需要重复对数组b操作,最终再根据求出数组b的前缀和数组,就可以在O(n + len(b))时间内求出结果。

那如何求出差分数组b? 假设数组c是数组d的差分数组,对d的某个区间加x,可以通过insert c完成,那么更新完c和d后,仍然满足c是d的差分数组,d是c的前缀和数组。

根据这一性质,我们把原来数组a每一项看成是0,对每一项进行加a[i]操作,变成现在的数组a,这个过程中数组b也进行insert操作,最终b仍是是现在a的差分数组。即:

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

二维差分

对于矩阵a中的某一块矩阵加c(这块矩阵左上角为x1, y1;右下角为x2, y2),也可以通过其差分矩阵b进行insert完成。

同样假设已有差分矩阵b,insert操作为:

void insert(int x1, int y1, int x2, int y2, int c) {
    b[x1][y1] += c;
    b[x1][y2 + 1] -= c;
    b[x2 + 1][y1] -= c;
    b[x2 + 1][y2 + 1] += c;
}

那么得出差分矩阵b也就很简单了,进行insert(i, j, i, j, a[i])即可。这一小块矩阵是1*1的,左上角和右下角相同。

要点:

二维的理解可以从面积上考虑,二维前缀:该点为左上角这块面积的和,二维差分:该点+c,影响右下角的前缀和。

写代码时从下标1开始。

posted @ 2021-11-19 18:05  moon_orange  阅读(37)  评论(0编辑  收藏  举报