算法心得(3)**差分**

**思路**

差分可以简单的看成**序列中每个元素与其前一个元素的差**一般认为它相当于前缀和的   逆运算  

 

一般在情况满足两个条件时就使用它:(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[1]+B[2],E[3]=B[1]+B[2]+B[3].......E[n]=B[1]+B[2]+B[3]+...+B[n]   

进行一些数学运算用来比较:

A[n]=B[n]+B[n-1]+B[n-2]+...+B[1]

  =E[n]

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)]++; // 时间不能小于1
        B[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;
        else
            cout << A[x + k] << endl; // 输出对应数组元素
    }
    return 0;
}

 

posted @ 2025-03-13 09:49  Travic  阅读(77)  评论(0)    收藏  举报