原题链接: AcWing 797. 差分
输入一个长度为 n 的整数序列。
接下来输入 m 个操作,每个操作包含三个整数 l,r,c,表示将序列中 [l,r] 之间的每个数加上 c。
请你输出进行完所有操作后的序列。
输入格式
第一行包含两个整数 n 和 m。
第二行包含 n 个整数,表示整数序列。
接下来 m 行,每行包含三个整数 l,r,c,表示一个操作。
输出格式
共一行,包含 n 个整数,表示最终序列。
数据范围
1≤n,m≤100000,
1≤l≤r≤n,
−1000≤c≤1000,
−1000≤整数序列中元素的值≤1000
输入样例:
6 3
1 2 2 1 2 1
1 3 1
3 5 1
1 6 1
输出样例:
3 4 5 3 4 2
题目大意:
将数组a处于下标在[l, r]区间内的元素加上c
思路:
首相想到的做法是遍历一遍区间,如果满足下标在[l,r]范围内就将元素加上c,这个做法的时间复杂度是O(n)的,那么有没有什么更快的算法呢?
差分与前缀合:
- 差分和前缀合互为逆运算
对于数组a,如果a中的元素满足a[i] = b[1] + b[2] + …… + b[i],那么称a[i]是b[i]的前缀合,数组a是数组b的前缀合数组;
此时数组b中的元素均满足 b[i] = a[i] - a[i-1],称b[i]是a[i]的差分,数组b就是数组a的差分数组
如何实现将数组a中下标在[l, r]的元素加上c?
如果将b[l] += c
, 那么a在[l, n]上的元素都会加上c(定义)
那么在将b[l] += c
之后,再将b[r+1] -= c
就可以实现a[r+1, n]上的元素都减去c
结果就是:只有a在[l, r]上的元素加上c
如图所示,红色部分加上c,绿色部分减去c,结果就是只有[l, r]区间内的元素加上c
因此,
通过对a的差分数组进行操作,只需要执行2条语句,时间复杂度为O(1);
再通过求前缀合来输出a数组
C++代码:
#include <iostream>
using namespace std;
const int maxn = 1e5 + 10;
int n, m;
int a[maxn], b[maxn];
int main(){
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
//构建差分数组b[n]
a[0] = 0;
for(int i = 1; i <= n; i ++ )
b[i] = a[i] - a[i-1];
//m个询问
while(m--){
int l, r, c;
scanf("%d%d%d", &l, &r, &c);
b[l] += c;
b[r+1] -= c;
}
//对b[n]求前缀合得到最终的a[n]
for(int i = 1; i <= n; i ++ ){
a[i] = a[i-1] + b[i];
printf("%d ", a[i]);
}
return 0;
}
做法二:
在求差分数组的时候,另外一种思路可以看做对[i, r]区域插入a[i]:
#include <iostream>
using namespace std;
const int maxn = 1e5 + 10;
int n, m;
int a[maxn], b[maxn];
// 插入函数
void insert(int l, int r, int c){
b[l] += c;
b[r+1] -= c;
}
int main(){
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
//用插入的方式构造查分数组b[n]
for(int i = 1; i <= n; i ++ )
insert(i, i, a[i]);
//m个询问
while(m--){
int l, r, c;
scanf("%d%d%d", &l, &r, &c);
insert(l, r, c);
}
//对b[n]求前缀合得到最终的a[n]
for(int i = 1; i <= n; i ++ ){
a[i] = a[i-1] + b[i];
printf("%d ", a[i]);
}
return 0;
}
复杂度分析:
- 时间复杂度:O(1),差分实现的增加只需要2条语句
- 空间复杂度:O(n),需要维护一个长度为n的差分数组