差分法~超详细(公式+原理+例题)

差分法

差分法应用在处理区间问题时比较多。一个数列要在很多不确定的区间内加上相同的一个数,如果直接在原数列上操作是非常复杂且耗时的,差分法就是先将数列拆分,构造一个拆分的新数列,在这个新数列上进行我们想要的操作之后,在将这个数列合并起来即可。

听起来好像复杂了很多,其实不然,这样处理反而能减少很多操作,比如不用对给定区间内的原数列的每一项进行操作,只需要在新数列上操作区间左右边界的这两项即可,这样就达到了减少耗时的效果。

定义:

对于已知有n个元素得离线数列d,我们可以建立记录它与每项与前一项得差值得差分数组f;显然,f[1] = d[1] - 0 = d[1]; 对于整数 i∈[2,n],我们让f[i]=d[i]-d[i-1]。将对d的一些操作转移至f数列,最终合并f得到d的一种操作,叫做差分法。

对于定义的通俗理解粒子:

①现在有一个数列d : 3   4   1   5   6   2   7   9

②我们根据公式 f[i]=d[i]-d[i-1] ,d[0]= 0 .构造一个新数列(差分数列) f: 3    1    -3    4    1    -4    5    2

③当我们需要对数列d的 [1,6]这区间的数加1,并求出区间加1后数列的值。

④我们只需要对区间的左边界L处项也就是f数列中第1项(值为3),加上一个1,对区间的右边界R处的下一个也就是R+1项也就是f数列中第7项(值为5),减去一个1,那么有改变后的f为:

⑤ f:4    1   -3    4    1    -4    4    2

⑥再根据f和d数列的关系,di = f1+f2+...+fi  ,di = d(i-1) + fi可以得出改变后的d:

⑦ d:4    5    2    6    7    3    7    9

⑧ 很明显,我们只操作了两项,也就是L项(左边界项)和R+1项(右边界的后一项),但是当我们合并f求出d的时候却得到了正确的d,也就是该区间的每一项都加上了1.

⑨有人会说这有啥?确定没啥吗?这只是一个区间,如果是很多个不确定的区间,比如[1,3], [3,6], [2,5], [4,7]......呢?我依旧只需要合并一次,但是在区间操作上,每给一个区间我都只需要操作两次(只需关注左右边界即可),这不是快了很多很多吗,不然每一个区间都需要额外的去加去操作,这必然带来很大的耗时。

差分法解决区间问题的原理:

当我们需要对数列d的某个区间所有项进行加m操作时,只需要将这个数列分解为一个差分数列 f 之后,对左区间边界 f[L] 项进行加m操作,对右边界 f[R+1]项进行减m操作,最后根据拆分时的公式:

di = f1+f2+..+fi=d(i-1)角标 + fi 反过来合并即可得到我们需要的数列d。不管多少个区间如此操作皆可。

这样能成功原理是什么呢?看图:

 差分法的用途:

最后在总结一下,主要有两个用途:

(1)快速处理区间加减操作:

假如现在对数列中区间[L,R]上的数加上x,我们通过性质(1)知道,第一个受影响的差分数组中的元素为f[L],即令f[L]+=x,那么后面数列元素在计算过程中都会加上x;最后一个受影响的差分数组中的元素为f[R],所以令f[R+1]-=x,即可保证不会影响到R以后数列元素的计算。这样我们不必对区间内每一个数进行处理,只需处理两个差分后的数即可;

(2)询问区间和问题:

由性质(2)我们可以计算出数列各项的前缀和数组sum各项的值;那么显然,区间[L,R]的和即为ans=sum[R]-sum[L-1];

那么如何只根据差分数列求出原始数列的前n项和吗?当然可以,如下图:

 

本菜在看到最终公式的时候有点理解不了,找了很多关于双重西格玛的求解公式还是不知道怎么得来的,后面自己代入推了一下:

 

最后上一道例题!

2020年12月22日的牛客网竞赛题B题牛牛浇树。

这题如果单纯的用暴力法是ac不了的,超时。

应该用差分法。

import java.util.*;
public class Solution {
    public int oddnumber (int n, int m, int[] l, int[] r) {
//这里的cf数组可以就理解为上面说过的f数组,也就是差分数组
      int[] cf = new int[200020];
        int i;
        for(i = 0; i < m; i++) {
            cf[l[i]]++;
            cf[r[i]+1]--;
        }
//到这一步的时候,也不需要另外建上面说过的d数组,直接用cf数组在原基础上还原d数组即可
//反正长度一样,且是前面cf所有项的和。
        for(i = 1; i <= n; i++){
            cf[i] = cf[i-1] + cf[i];
        }
//最后判断即可,加上自然生长m天也就是m厘米对2取余即可。
        int res = 0;
        for(i = 1; i <= n; i++) {
            if((cf[i] + m) % 2 == 1){
                res++;
            }
        }
        return res;
    }
}

谢谢大家,写得并不是很清晰,如果有错误的地方欢迎指正!🙂

 

 

 

  • 123
    点赞
  • 302
    收藏
    觉得还不错? 一键收藏
  • 14
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值