算法初探系列7——前缀和与差分之差分

概述

这节课我们将学习和前缀和很像的算法——差分(建议从前缀和算法开始看)。

代入问题

  • 给定长度为n的a数组,和q次操作。
  • 每次操作包含一个三元组(l,r,x),表示将a[l]到a[r]这段数中的每个数都加上x。
  • 求q次操作后的a数组。

法1:模拟

每次遍历l到r,手动累加。
时间复杂度:O(nq),即q次操作,每次最多遍历n个数。
空间复杂度:O(n),不用开额外数组。

法2:一维差分

一维差分数组的定义

定义b数组,符合以下条件:

  • ∀a[i] (1 <= i <= n) = b[1] + b[2] + … + b[i]

此时b数组就是a的查分数组啦~
细心的小伙伴会发现,a数组也是b的前缀和数组,即前缀和与差分互为逆运算

如何算出一维差分数组

利用前缀和与差分互为逆运算的特点,可知:
在这里插入图片描述

如何用一个数组退到出另一个数组

在这里插入图片描述

如何使用一维差分数组

对于每一次操作,我们可以直接让b[l] += x,即:
在这里插入图片描述
别急!!不要以为这样就结束了,因为…
在这里插入图片描述
这个操作直接将a[l]到a[n]全部加了x,所以我们要将b[r + 1] -= x,即将a[r + 1]到a[n]这段数都减x。

效率总结

时间复杂度:O(n),即O(n)预处理,每次查询O(1)
空间复杂度:O(n),需要开一个差分数组。

上代码

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
int a[N], b[N]; 
int main() {
    int n, q;
    cin >> n >> q;
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
        b[i] = a[i] - a[i - 1];
    }
    while (q--) {
        int l, r, x;
        cin >> l >> r >> x;
        b[l] += x;
        b[r + 1] -= x;
    }
    for (int i = 1; i <= n; ++i) {
        b[i] += b[i - 1];
        cout << b[i] << ' ';
    }
    return 0;
}

二维差分

  • 给定a[n][m]和q次操作。
  • 每次操作给出x1, y1, x2, y2和t,表示将左上角坐标为(x1,y1),右下角坐标为(x2,y2)的矩阵中每个数都加上t。
  • 求最终的a数组。

法1:模拟

遍历,手动累加。
时间复杂度:O(qn2),即q次操作,每次最多遍历n2次。
空间复杂度:O(n2),即不用开额外数组。

法2:二维差分

如何执行一次操作

在这里插入图片描述
在此图中:
在这里插入图片描述

上代码

#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 5;
int a[N][N], b[N][N];
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];
        }
    }
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) {
            b[i][j] += a[i][j];
            b[i + 1][j] -= a[i][j];
            b[i][j + 1] -= a[i][j];
            b[i + 1][j + 1] += a[i][j];
        }
    }
    while (q--) {
        int x1, y1, x2, y2, t;
        cin >> x1 >> y1 >> x2 >> y2 >> t;
        b[x1][y1] += t;
        b[x2 + 1][y1] -= t;
        b[x1][y2 + 1] -= t;
        b[x2 + 1][y2 + 1] += t;
    }
    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) {
            cout << b[i][j] << ' ';
        }
        cout << '\n';
    }
    return 0;
}

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蒟蒻一枚

谢谢鸭~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值