文章目录
概述
这节课我们将学习和前缀和很像的算法——差分(建议从前缀和算法开始看)。
代入问题
- 给定长度为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;
}