差分算法:
1.差分的原理:
对于原始数组a[],差分数组diff[]的定义是:
diff[i] = a[i] - a[i - 1]
这是因为:
diff[1] = a[1]
diff[2] = a[2] - a[1]
diff[3] = a[3] - a[2]
…
diff[n] = a[n] - a[n - 1]
我们可以发现,对差分数组进行前缀和操作,可以让它变成原数组,因为:
diff[1] + diff[2] + diff[3] + … + diff[i]
= a[1] + (a[2] - a[1]) + (a[3] - a[2]) + … + (a[i] - a[i - 1])
= a[i]
2.差分的特点:
利用差分数组可以实现快速的区间修改,下面是区间[l, r]都加上x的方法:
diff[l] += x;
diff[r + 1] -= x;
这段代码的含义是:
现将区间[l, n]都加上x,但是呢,[r + 1, n]我们并不需要加上x,但是之前我们删除过一次了,所以我们需要减掉它。
在修改完成之后,我们需要做前缀和将它恢复为原数组。
恢复代码如下:
for(int i = 1; i <= n; i++){ // 开始求差分数组
diff[i] = a[i] - a[i - 1];
}
while(q--){
int l, r, c;
cin >> l >> r >> c;
// 开始进行修改操作
diff[l] += c;
diff[r + 1] -= c;
}
// 修改完之后,将结果运用到原数组里面
for(int i = 1; i <= n; i++){
a[i] = a[i - 1] + diff[i];
}
这样子之后,区间l ~ r都会加上c。
差分例题:
例题1:蓝桥OJ3693 肖恩的投球游戏:
链接如下:
https://www.lanqiao.cn/problems/3693/learning/?page=1&first_category_id=1&name=%E8%82%96%E6%81%A9%E7%9A%84%E6%8A%95%E7%90%83%E6%B8%B8%E6%88%8F
解题思路:
我们可以发现,这道题就是一个典型的差分和前缀和相关的题目,我们需要对原数组进行差分求出差分数组,然后循环进行q次操作
让它每次都在区间[l, r]上都能加上c个数字,进行完q次操作之后,就将结果运用到原数组中去,最后输出结果:
代码如下:
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int a[N], diff[N]; // 原数组和差分数组
int main()
{
// 请在此输入您的代码
int n, q;
cin >> n >> q;
// 初始化原数组和差分数组
a[0] = 0;
diff[0] = 0;
for(int i = 1; i <= n; i++){
cin >> a[i];
}
for(int i = 1; i <= n; i++){
diff[i] = a[i] - a[i - 1];
}
while(q--){
int l, r, c;
cin >> l >> r >> c;
// 开始进行修改操作
diff[l] += c;
diff[r + 1] -= c;
}
// 修改完之后,将结果运用到原数组里面
for(int i = 1; i <= n; i++){
a[i] = a[i - 1] + diff[i];
}
// 输出结果
for(int i = 1; i <= n; i++)
cout << a[i] << " \n"[i == n];
return 0;
}
例题2:肖恩的投球游戏加强版(蓝桥OJ3694)
链接如下:
https://www.lanqiao.cn/problems/3694/learning/?page=1&first_category_id=1&name=%E8%82%96%E6%81%A9%E7%9A%84%E6%8A%95%E7%90%83%E6%B8%B8%E6%88%8F
解题思路:
这个跟上一道题目不太一样,我们发现这个是需要二维的进行差分操作,再利用前缀和还原到原数组中。但是总体思路是不变的。
二维的差分是这样的:
D[x1][y1] += c; D[x1][y2 + 1] -= c;
D[x2 + 1][y1] -= c; D[x2 + 1][y2 + 1] += c;
所以二维的原数组还原是这样的:
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
D[i][j] += D[i - 1][j] + D[i][j - 1] - D[i - 1][j - 1];
a[i][j] += D[i][j];
}
代码如下:
#include <iostream>
using namespace std;
const int N = 1e3 + 9;
int a[N][N], diff[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] , diff[i][j] = a[i][j] - a[i][j-1];
while(q--)
{
int x1,x2,y1,y2,c; cin >> x1 >> y1 >> x2 >> y2 >> c;
for(int i = x1 ;i <= x2 ;i++)
diff[i][y1] += c , diff[i][y2+1] -= c;
}
for(int i = 1 ;i <= n ;i++)
for(int j = 1 ;j <= m ;j++)
a[i][j] = a[i][j-1] + diff[i][j];
for(int i = 1 ;i <= n ;i++)
{
for(int j = 1 ;j <= m ;j++)
cout << a[i][j] << ' ';
cout << '\n';
}
return 0;
}
我们也可以使用这个代码:
#include <bits/stdc++.h>
using namespace std;
#define MAX_N 1000
int a[MAX_N + 5][MAX_N + 5], D[MAX_N + 5][MAX_N + 5] = { 0 };
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];
}
}
while (q--) {
int x1, y1, x2, y2, c; cin >> x1 >> y1 >> x2 >> y2 >> c;
D[x1][y1] += c; D[x1][y2 + 1] -= c;
D[x2 + 1][y1] -= c; D[x2 + 1][y2 + 1] += c;
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
D[i][j] += D[i - 1][j] + D[i][j - 1] - D[i - 1][j - 1];
a[i][j] += D[i][j];
cout << a[i][j] << " ";
}
cout << endl;
}
return 0;
}
总结:
- 差分可以用前缀和来求出原数组
- 差分可以用来进行区间的数量的增加和减少,前提是操作前数组并没有进行改变,并且要在所有的修改操作完成之后,才能进行查询操作
写在最后:
a[i][j] += D[i][j];
cout << a[i][j] << " ";
}
cout << endl;
}
return 0;
}
总结:
- 差分可以用前缀和来求出原数组
- 差分可以用来进行区间的数量的增加和减少,前提是操作前数组并没有进行改变,并且要在所有的修改操作完成之后,才能进行查询操作
写在最后:
我们可以用这个网站学习C++知识:https://github.com/0voice