差分数组---区间修改

什么是差分数组

问题背景
如果给你一个包含5000万个元素的数组,然后会有频繁区间修改操作,那什么是频繁的区间修改操作呢?比如让第1个数到第1000万个数每个数都加上1,而且这种操作时频繁的。
此时你应该怎么做?很容易想到的是,从第1个数开始遍历,一直遍历到第1000万个数,然后每个数都加上1,如果这种操作很频繁的话,那这种暴力的方法在一些实时的系统中可能就拉跨了。
因此,今天的主角就出现了——差分数组。

算法原型
比如我们现在有一个数组arr,arr={0,2,5,4,9,7,10,0}
在这里插入图片描述

那么差分数组是什么呢?其实差分数组本质上也是一个数组,我们暂且定义差分数组为d,差分数组d的大小和原来arr数组大小一样,而且d[i]=arr[i]-arri-1,且d[i]=0,它的含义是什么?就是原来数组i位置上的元素和i-1位置上的元素作差,得到的值就是d[i]的值。
所以,例子中的arr数组其对应的差分数组值如下图所示。
在这里插入图片描述

那么构造了这么个玩意有什么用呢?难道是来浪费宝贵的内存空间的?嗯,确实是来浪费宝贵的内存了,但是却换了时间上的高效。
现在我们有这么一个区间修改操作,即在区间1~4上,所有的数值都加上3.
在这里插入图片描述
我们不要傻傻地遍历arr数组的[1,4]范围,然后再分别给每个值加上3,我们此时更改差分数组d即可。
显而易见,差分数组d在[2,4]范围内的值都不用改变,只需要改变差分数组位置1和位置5的值即可,即d[1]=d[1]+3,而d[5]=d[5]-3,其余不变,为什么呢?因为差分数组的定义——d[i]=arr[i]-arr[i-1]

现在,我们如何根据差分数组d来推测arr中某一个位置的值呢?
比如,此时,我们想知道arr[1]的值,我们不能直接通过arr[1]得到原来的值,因为在区间修改的操作中我们并没有修改arr的值,因此我们必须从前往后遍历递推,由于d[0]=arr[0]-0(我们定义arr[0]的前一个数为0),那么arr[0]=d[0]=0,又由于d[1]=arr[1]-arr[0]=5,那么arr[1]=5+arr[0]=5。以此类推,由于d[2]=arr[2]-arr[1]=3,所以arr[2]=3+arr[1]=8。

用途:

(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];

练手题目

1.[HDU1556]Color the ballDescription-N个气球排成一排,从左到右依次编号为1,2,3…N.每次给定2个整数a b(a <= b),lele便为骑上他的“小飞鸽"牌电动车从气球a开始到气球b依次给每个气球涂一次颜色。但是N次以后lele已经忘记了第I个气球已经涂过几次颜色了,你能帮他算出每个气球被涂过几次颜色吗?
-Input:每个测试实例第一行为一个整数N,(N <= 100000).接下来的N行,每行包括2个整数a b(1 <= a <= b <= N)。当N = 0,输入结束。
-Output:每个测试实例输出一行,包括N个整数,第I个数代表第I个气球总共被涂色的次数。
Solution
1.记录各次操作,对差分数组进行对应修改,改变量为1(用途1);
2.使用性质(1)计算各项的值即可;


#include<iostream>

#include<cstdio>

#include<cmath>

#include<cstring>

#include<algorithm>

using namespace std; 

int d[100010],a[100010],l,r;

int main(){

    int n;

    while(scanf("%d",&n),n)

    {

        memset(d,0,sizeof(d));

        memset(a,0,sizeof(a));

        for(int i=1;i<=n;++i){

            scanf("%d%d",&l,&r);

            d[l]+=1;

            d[r+1]-=1;

        }

        for(int i=1;i<=n;++i) a[i]=a[i-1]+d[i];

        for(int i=1;i<n;++i) printf("%d ",a[i]);

        printf("%d\n",a[n]);

    }

    return 0;

}

2.[NKOJ3754]数列游戏Description-给定一个长度为N的序列,首先进行A次操作,每次操作在Li和Ri这个区间加上一个数Ci。
然后有B次询问,每次询问Li到Ri的区间和。
初始序列都为0。
-输入格式:
第一行三个整数N A B。(1<=N<=1000000,1<=A<=N,A<=B<=N)
接下来A行,每行三个数Li Ri Ci。(1<=Li<=N,Li<=Ri<=N,|Ci|<=100000000000000)。
接下来B行,每行两个数 Li Ri。范围同上。
-输出格式:
对于每次询问,输出一行一个整数。
因为最后的结果可能很大,请对结果mod 1000000007。
Solution
1.应用(1)处理区间加;
2.用性质(1)求出修改后数列,再求出相应数列和(应用2)或直接用性质(2)求解;
3.注意随时取模


#include<iostream>

#include<cmath>

#include<cstring>

#include<algorithm>

#include<cstdio>

const long long mod=1000000007; 

using namespace std;

long long d[100010],f[100010],sum[100010];

int main(){

    int n,a,b;

    scanf("%d%d%d",&n,&a,&b);

    memset(d,0,sizeof(d));

    memset(f,0,sizeof(f));

    memset(sum,0,sizeof(sum));

    for(int i=1;i<=a;++i){

        long long l,r,c;

        scanf("%ld%ld%ld",&l,&r,&c);

        f[l]=(f[l]+c)%mod;

        f[r+1]=(f[r+1]-c)%mod;

    }

    for(int i=1;i<=n;++i)  d[i]=(d[i-1]+f[i])%mod;

    for(int i=1;i<=n;i++) sum[i]=(sum[i-1]+d[i])%mod;

    for(int i=1;i<=b;++i){

        int l,r;

        scanf("%d%d",&l,&r);

        printf("%ld\n",(sum[r]-sum[l-1])%mod);

        //printf("%ld\n",temp>=0?temp:temp+mod);//防止结果为负; 

    }

    return 0;

} 

3.[NOIP2012提高&洛谷P1083]借教室题解随笔:http://www.cnblogs.com/COLIN-LIGHTNING/p/8467795.html
4.[洛谷P3948]数据结构题解随笔:http://www.cnblogs.com/COLIN-LIGHTNING/p/8482463.html转载于:https://www.cnblogs.com/COLIN-LIGHTNING/p/8436624.html

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值