一维差分思想【算法推导、深刻思考】

797. 差分 - AcWing题库

差分本质上就是前缀和的逆运算

算法推导

其实在最开始自己去完成这个题目的时候,感觉好像是可以往前缀和方向靠的,但是一下子没有想到实现方法就无疾而终了。所以最后选择的算法就只是单纯的暴力(虽然知道过不了,但是起码实现一下)

暴力代码

#include

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

在哪个位置呢?

一维差分思想【算法推导、深刻思考】_第1张图片

left开始的位置让数组都加上一个c

right + 1开始的位置后面都减去一个c

所以这里我们就可以用O(1)的时间复杂度处理数据了

代码模板

#include

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,得到这样一个数组

一维差分思想【算法推导、深刻思考】_第2张图片

继续输入后,最后得到

一维差分思想【算法推导、深刻思考】_第3张图片

开始执行插入操作 [ 1 , 3 ]都需要 +1

一维差分思想【算法推导、深刻思考】_第4张图片

我们计算一下这个前缀和的结果

  • A1:(1+1)+0 = 2
  • A2:A1 + 1 = 3
  • A3: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)时间复杂度完成,而不是通过循环(这里就需要用到前缀和的思想,先将每一次的操作存起来,不修改原始数组,后面全部加起来就可以得到结果)

你可能感兴趣的:(PTA,前缀和,差分思想,算法,c++)