**思路**
差分可以简单的看成**序列中每个元素与其前一个元素的差**一般认为它相当于前缀和的 逆运算
一般在情况满足两个条件时就使用它:(1)影响可以累加(2)有多个影响
差分序列的作用:快速一个序列中某个区间内的所有值同时加上或减去一个常数
拿给一维数组A来说:
定义一个操作(l,r,k),表示数组中对A[l]~A[r]区间上的每一个元素都加上k,如操作(3,6,2)使得数组A变成:
当有多个操作时,如果此时再对元素一个一个的加来加去,显然太慢了,并且有的操作还会有重叠,比如对数组A,分别进行操作(3,6,2),操作(4,7,-1):
可以看到A[4]、A[5]、A[6]被反复操作了两次。
使用差分可以优化重叠的部分。定义差分数组B:
B[1]=A[1],B[2]=A[2]-A[1],B[3]=A[3]-A[2].......B[n]=A[n]-A[n-1]
注意得到:
B[1]+B[2]+B[3]+...+B[n]=A[n]
有了上述关系,对于操作(l,r,k),只需要对差分数组B:
B[l]+=k
B[r+1]-=k
当操作很多的时候,就可以先算出差分数组,再用以上式子对差分数组修改,最后重新对修改过的差分数组进行累加便可得结果数组C。
**原理**
将结果C数组拆成[1,l-1]、[l,r]、[r+1,n]三个部分:
对于[1,l-1],因为B[1]到B[l-1]没有改变,所以该部分等于原来数组A的值。
对于[l,r]:
C[l]=B[l],因为B[1]+=k,所以C[1]=A[l]+k
C[l+1]=B[l]+B[l+1],因为B[1]+=k,所以C[l+1]=A[l+1]+k
.........
C[r]=B[l]+B[l+1]+B[l+2]+....+B[r],因为B[l]+=k,所以C[r]=A[r]+k
对于[r+1,n]:
C[r+1]=B[l]+B[l+1]+B[l+2]+....+B[r]+B[r+1],因为B[l]+=k,B[r+1]-=k,所以C[r+1]=A[r+1]
.........
C[n]=B[l]+B[l+1]+B[l+2]+....+B[r]+B[r+1]+....+B[n],因为B[l]+=k,B[r+1]-=k,所以C[n]=A[n]
**思考**
差分与前缀和的区别是:前缀和记录的是影响,而差分处理的是被影响的对象。
那为什么说差分是前缀和的逆运算呢?
我们看一下对于一维数组什么时候使用前缀和:
设查询[l,r]为计算一维数组A中A[l]~A[r]的值的总和,那么我们可以使用前缀和数组D:
D[1]=A[1]
D[2]=D[1]+A[2]=A[1]+A[2]
D[3]=D[2]+A[3]=A[1]+A[2]+A[3]
D[4]=D[3]+A[4]=A[1]+A[2]+A[3]+A[4]
..........
D[n]=D[n-1]+A[n]=A[1]+A[2]+A[3]+........+A[n]
所以 查询[l,r]=D[r]-D[l-1]
可以看到前缀和是先加后减,差分是先减后加。这时候我们再使用一个数组E,用来保存我们之前得到的差分数组B的前缀和:
E[1]=B[1],E[2]=B[2]-B[1],E[3]=B[3]-B[2].......E[n]=B[n]-B[n-1]
进行一些数学运算用来比较:
A[n]-A[n-1] =B[n]
=E[n]-E[n-1]
A[r]-A[l-1]=A[r]-A[r-1]+A[r-1]-A[r-2]+A[r-2]-A[r-3]+......+A[l]-A[l-1]
=B[r]+B[r-1]+B[r-2]+......+B[l]
=E[r]-E[l-1]
可以看到数组A与数组E等效!!!
相当于:A差分得到B,B前缀和得到A
由此可以看出两者的逆运算关系
**例题**
第25次CSP认证考试中的第二题,“出行计划”:
虽然这道题用到了差分,但是我认为最关键的技巧不在于差分算法,而在于数据结构。
我们用一个数组A用来保存可以进入的场所数量,其下标为时间,其值为在该时间能进入的场所数量。比如A[1]=2,表示在核酸得出的时间t=1时,能够进入2个场所。
要在ti时间到达一个地方,并且该地方还要你出示ci时间内的健康码,那我们的健康码出来的时间最早应会在ti-ci+1,为什么+1呢,这是题目规定的,健康码的有效期是pi+k到pi+k+23,相当于从0开始计时。
所以对于一个计划(ti,ci),我们对于数组A的操作,应该让A[ti-ci+1]~A[ti]之内的值都+1。这时候就可以使用差分了。
通过差分快速获得数组A后,便可以根据查询的时间q和核酸时间k,直接输出A[q+k]即可。
一个题解代码:
#include <bits/stdc++.h>
#define LL long long
#define N 1000005
using namespace std;
int n, m, k, ans, x;
int t[N], c[N];
int A[N]; // 结果数组(差分求和数组
int B[N]; // 差分数组
int main()
{ios::sync_with_stdio(false);cin.tie(0);cin >> n >> m >> k;for (int i = 1; i <= n; i++) // 注意下标的起始{cin >> t[i] >> c[i];B[max(1, t[i] - c[i] + 1)]++; // 时间不能小于1B[t[i] + 1]--;}for (int i = 1; i <= t[n]; i++){A[i] += B[i]; // 对差分数组求和}while (m--){cin >> x;if (x + k > t[n]) // 如果核酸出结果时间比我们最后一次到地方的时间还晚,就输出0,根本不能去任何一个地方cout << 0 << endl;elsecout << A[x + k] << endl; // 输出对应数组元素}return 0;
}