差分①

· · 算法·理论

差分简述

前缀和

说到差分,我们先来说一下前缀和。

还记得什么是前缀和吗?它是静态求前缀和的一种工具。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 加上了 2diff_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; } ```