797. 差分 - AcWing题库
差分本质上就是前缀和的逆运算
算法推导
其实在最开始自己去完成这个题目的时候,感觉好像是可以往前缀和方向靠的,但是一下子没有想到实现方法就无疾而终了。所以最后选择的算法就只是单纯的暴力(虽然知道过不了,但是起码实现一下)
暴力代码
#include<iostream>
using namespace std;
const int N = 100008;
int a[N] , b[N];
int main(){
int n , m;
cin >> n >> m;
for(int i = 1 ; i <= n ; i ++){
cin >> a[i];
}
int l , r , c;
while(m --){
cin >> l >> r >> c;
for(int i = l ; i <= r ; i ++){
b[i] += c;
}
}
for(int i = 1 ; i <= n ; i ++){
cout << a[i] + b[i] << " ";
}
return 0;
}
这样子的话时间复杂度会花费
-
N个输入时间
-
最多N * N个处理
b[i]
数组因为有m个处理请求(而m最大和N差不多)
-
N个输出时间
所以这里的时间复杂度将是O(n^2^)
,必然是需要优化的
能处理哪里?
输入和输出的时间时不可避免的,唯一可以处理的就是如何更快速的完成[ l , r ]
区间内加上c的操作
优化处理操作
我们可以构造一个数组b
使得a [i]
是b的前缀和,那么b数组就称a数组的差分
也就是a[3] = b1 + b2 + b3
- b1 = a1
- b2 = a2 - a1
- b3 = a3 - a2
所以,我们只需要求b数组的前缀和就能得到a[i]
若此时我们拥有B数组,那么只需要O(n)
的时间就可以得到A(操作后的)
重新理解一下题意
题目要求我们对A数组[ l , r ]
区间内,每一个都加上c。
即ai + c,ai+1 +c ,…,a~i + r - l~ + c
如果我们使用差分,我们可以使用O(1)
的时间复杂度实现
因为A数组是B数组的前缀和,所以我们让Bleft + c后,那么Aleft 后面的每一位都将加上c
为什么呢?
因为A数组是B数组的前缀和(a2 = b2 + b1、a3 = b3 + b2 + b1),所以在Aleft之后,所有的值都将加上c
但是这样显然是不行的,因为我们只是需要在[ l , r ]
上加上c,所以我们需要在某一个位置进行-r
。
在哪个位置呢?
在
left
开始的位置让数组都加上一个c在
right + 1
开始的位置后面都减去一个c所以这里我们就可以用
O(1)
的时间复杂度处理数据了
代码模板
#include<iostream>
using namespace std;
const int N = 100008;
int a[N] , b[N];
void insert(int l , int r , int c){
b[l] += c;
b[r + 1] -= c;
}
int main(){
int n , m ;
cin >> n >> m ;
for(int i = 1 ; i <= n ; i ++){
cin >> a[i];
insert(i , i , a[i]);
}
int l , r , c;
while(m --){
cin >> l >> r >>c;
insert(l , r , c);
}
for(int i = 1 ; i <= n ; i ++){
b[i] += b[i - 1];
cout << b[i] << " ";
}
return 0;
}
其实,这里在代码中并没有很显然的表示 A,B两个数组
b[i] += b[i - 1];
就是将B数组变成自己的前缀和——其实也就是我们上面推导公式所说的A数组(前缀和)
问题一:为什么要在输入的时候执行Insert操作?
其实要解释的就是为什么要执行
b[r + 1] -= c;
我们可以完整的执行一遍操作理解,首先假设就是需要执行,我们模拟一遍
输入第一个数1,得到这样一个数组
继续输入后,最后得到
开始执行插入操作
[ 1 , 3 ]
都需要 +1我们计算一下这个前缀和的结果
A1
:(1+1)+0 = 2A2
:A1 + 1 = 3A3
:A2 + (-1) = 2我们尝试带回原数组看看结果是否一致,处理
1 2 2 1 2 1
,在1~3的位置+1,结果就是2 3 2
,结果是对的为什么呢?
其实我们仔细去看之前的原始推导式就不能发现,我们是希望在
[ l , r ]
区间上都加上c,但是如果只在左侧加上,那么后面都会加上c,所以我们需要在r + 1
处-c
才能保证结果一致假设我就是不加,那意义是什么呢?
那就将导致从
[ l , n ]
都执行 +c操作,后面的数据将会越来越大(因为后面我们的输出是以前缀和形式输出的)
问题二:为什么要以前缀和的形式输出呢?也就是为什么要执行b[i] += b[i - 1];呢?
- 因为在开始处理b数组时,我们有对
b [ r + 1]
进行-c
处理,就是为了保证以前缀和的形式输出!- 为了将
[ l , r ]
的处理以O(1)时间复杂度完成,而不是通过循环(这里就需要用到前缀和的思想,先将每一次的操作存起来,不修改原始数组,后面全部加起来就可以得到结果)