算法笔记(5)前缀和与差分

目录

引入

前缀和

差分


引入

对于一组数据,假设10个数为

9 3 4 2 6 5 8 7 10 1

要求我们求出第1个到第3个数的和,我们很容易想到以下代码

int sum=0;
for(int i=1;i<=3;i++) {
    sum+=a[i];
}
cout<<sum<<endl;

如果再次要求,求出第1个数到第8个数之间的和,那简单嘛,再遍历一次。

int sum=0;
for(int i=1;i<=8;i++) {
    sum+=a[i];
}
cout<<sum<<endl;

如果再再要求,求出第4个数到第8个数之间的和,那...再遍历嘛

int sum=0;
for(int i=4;i<=8;i++) {
    sum+=a[i];
}
cout<<sum<<endl;

如果再再再要求,求出第2个数到第6个数之间的和,那..那...再遍历嘛

如果.........

于是这种就是典型的区间和问题。通常的代码是这样的:

int m;cin>>m;
while(m--) {
    int l,r,sum=0;
    cin>>l>>r;
    for(int i=l,i<=r;i++) {
        sum+=a[i];
    }
    cout<<sum<<endl;
}

打住打住,我们之前不是已经遍历过1~3和1~8了吗,仔细想一想,4~8之间的和,是不是就是1~8的和减去1~3的和?

所以可不可以像记忆化递归那样?进行一个记忆储存?把1~3的值储存下来,把1~8的值储存下来,然后直接相减,岂不美哉?

那答案想必都知道,肯定可以的,这就是前缀和算法。

前缀和

前缀和例题:

洛谷P5638 光骓者的荣耀

P4702 前缀和+博弈论

P6568 贪心+前缀和

前缀和算法是基于递推的衍生算法,建以先把递推搞懂。

这里有两个数列

   9     3     4     2     6     5     8     7    10     1 (这个是刚才例子的数列)

a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8] a[9] a[10]

   9   12    16   18   24   29   37   44   54    55 

b[1] b[2] b[3] b[4] b[5] b[6] b[7] b[8] b[9] b[10]

仔细观察观察,下面的b数组,对应的b[ i ]的值,就是a[1]+a[2]+....+a[ i ] 的和。像这样,我们就完成了数组求和的储存。

所以求解a[4]+a[5]+a[6]+a[7]+a[8] 也就等于 b[8]-b[3]=44-16=2+6+5+8+7=28。

利用递推的思想,我们不难发现其实b[ i ]还等于 b[ i -1 ]+a[ i ]。

但由于有时候原数组a[n],最初的值意义已经不大,所以可以完全覆盖,也就是说直接前缀求和修改a数组

for(int i=2;i<=n;i++) {
    a[i]+=a[i-1];
}//前缀求和
//这时候a[n]的值就是原数组的和

此时a数组,就完全与上面例子中的b数组一样了。 

for(int i=n-1;i>=1;i--) {
    a[i]+=a[i+1];
}//像这样,从后往前加,就是后缀求和
//这时候a[1]的值就是原数组的和

swust oj#509 后缀和例题

差分

差分例题:

P2367 语文成绩

二维差分:P3397 地毯

P3406 海底高铁

差分也就是前缀和的逆运算

还是刚才的例子

   9     3     4     2     6     5     8     7    10     1 

a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8] a[9] a[10]

   9   12    16   18   24   29   37   44   54    55 

b[1] b[2] b[3] b[4] b[5] b[6] b[7] b[8] b[9] b[10]

从a数组到b数组,就是前缀和, 那么从b数组到a数组,就是差分了。

for(int i=n;i>=2;i--) {
    a[i]-=a[i-1];
}//差分代码

差分的意义就是区间修改,让原数组的第 i 项到第 j 项都加某个数,或者都减某个数。

例如:

   1     2     2     4     3     1    2

a[1] a[2] a[3] a[4] a[5] a[6] a[7] 

差分一下(当然,可以再前缀和回到原数组)

   1     1     0     2    -1    -2    1

b[1] b[2] b[3] b[4] b[5] b[6] b[7] 

如果说让原数组第2项到第5项加上2

   1     4         6     5     1    2

a[1] a[2] a[3] a[4] a[5] a[6] a[7] 

那么再将该数组差分一下

   1     3     0     2    -1    -4    1

b[1] b[2] b[3] b[4] b[5] b[6] b[7] 

再对比一下原来的差分数组:我们可以发现差分后,b[2]加了2,b[6]减去了2。

因此,对于让原数组的第 i 项到第 j 项都加某个数,其实可以通过差分,让差分数组第 i 项加上该数,让差分数组第 j+1 项减去该数,即可达到目的。

同理,对于让原数组的第 i 项到第 j 项都减某个数,让差分数组第 i 项减去该数,让差分数组第 j+1 项加上该数,即可达到目的。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青衿白首志

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值