差分①
_yang_yi_bo_
·
·
算法·理论
差分简述
前缀和
说到差分,我们先来说一下前缀和。
还记得什么是前缀和吗?它是静态求前缀和的一种工具。sum_i =a_1+a_2+a_3+...+a_i,进行 t 次求区间和,时间复杂度仅为 O(t)。
差分
我们在来说差分,它被应用于多次修改区间。差分数组 diff_i=a_i-a_{i-1},也就是记录了当前元素与前一个元素的差。
那他它有什么应用呢?
如下图,我们将区间 a_{[2,4]} 分别加上了 2 结果差分数组只在 diff_2 加上了 2,diff_5 减去了 2。可见,差分可以用 O(1) 的时间复杂度对区间进行修改。
还有一个性质:
如下图,我们将 a 的差分数组进行取前缀和后,变回了原来的数组。
运用这两个性质,我们可以解决很多问题。
U69096 前缀和的逆
这题并不是很困难,我们运用性质二,用 a_i-a_{i-1} 就行了。
#include<bits/stdc++.h>
using namespace std;
int n;
int a[10005];
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}for(int i=1;i<=n;i++){
cout<<a[i]-a[i-1]<<" ";
}
return 0;
}
P2367 语文成绩
这题就是简单的差分板子题。
#include<bits/stdc++.h>
using namespace std;
int n,m,ans=2e9+5;
int a[10000005],diff[10000005];
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
diff[i]=a[i]-a[i-1];
}for(int i=1;i<=m;i++){
int l,r,v;
cin>>l>>r>>v;
diff[l]+=v;
diff[r+1]-=v;
}for(int i=1;i<=n;i++){
diff[i]+=diff[i-1];
ans=min(ans,diff[i]);
}cout<<ans;
return 0;
}
接下来的两道题难度偏高。
CF1442A Extreme Subtraction
让所有的差分数组归零,就能让整个数组全部归零。
在 diff_i ≥ 0 时,就一定能让这个数归零。
比如 a 数组为 1,2,3 时,很容易构造出如何让所有数归零。
这题思路就很清晰了,若 $diff_i<0$,$ans$ 就 $+=diff_i$,最后判断 $diff_1$ 是否大于等于 $ans$ 的绝对值就行了(大于等于输出`YES`,小于输出`NO`)。
```cpp
#include<bits/stdc++.h>
using namespace std;
int n,a[30005],diff[30005],t;
void work(){
int sum=0;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}for(int i=1;i<=n;i++){
diff[i]=a[i]-a[i-1];
if(diff[i]<0){
sum-=diff[i];
}
}if(sum<=a[1]){
cout<<"YES";
}else{
cout<<"NO";
}cout<<"\n";
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>t;
while(t--){
work();
}
return 0;
}
```
### [P7404 [JOI 2021 Final] とてもたのしい家庭菜園 4](https://www.luogu.com.cn/problem/P7404)
首先,我们怎么判断在哪个点由上升变成下降呢?我们可以枚举点 $i$ ,再将枚举出来的所有答案取最小值。
我们可以用差分数组维护两数之间的差。并用前缀和数组存储要修改多少个点才能让 $a_1$ 到 $a_i$ 单调不减(也就是$diff_1$ 到 $diff_i$有多少个负数),用后缀和数组存储要修改多少个点才能让 $a_{i+1}$ 到 $a_n$ 单调不增(也就是$diff_1$ 到 $diff_i$有多少个正数)。
```cpp
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n;
int a[200005],diff[200005],sum1[200005],sum2[200005],ans=2e18+1;
signed main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
diff[i]=a[i]-a[i-1];
}for(int i=1;i<=n;i++){
sum1[i]=sum1[i-1]+(diff[i]>0?0:abs(diff[i])+1);
}for(int i=n;i>=1;i--){
sum2[i]=sum2[i+1]+(diff[i]<0?0:abs(diff[i])+1);
}for(int i=1;i<=n;i++){
ans=min(ans,max(sum1[i],sum2[i+1]));
}
cout<<ans<<"\n";
return 0;
}
```