浅谈前缀和与差分(一维数组)
前缀和:其实可以理解为数组前n项的和,前缀和数组s[i]=a[1]+a[2]......+a[n]。s[i] = s[i-1] + a[i] !
举个例子来实现下把:比如数组a[1]=7,a[2]=7,a[3]=5,a[4]=8,a[5]=2,a[6]=5,a[7]=8,每次需要询问序号 i 到 j 的区间和,暴力解法复杂度O[n],可当查询次数很多范围很大的时候,无疑会超时,原因很明显,重复计算太多了。我们如果先提前算好了每一个位置的前缀和,然后用s[j]-s[i-1],结果不就是我们这次询问的答案吗?O(1)查询不香吗?
前缀和数组我们可以在读数据的时候便可以完成了
for(int i=1;i<=n;i++){ scanf("%d",&a[i]); s[i]=s[i-1]+a[i];//前面的和+当前值 }
前缀和数组的值为:s[1]=7,s[2]=14,s[3]=19,s[4]=27,s[5]=29,s[6]=34,s[7]=42
询问 i 到 j 的区间和,直接输出 s[j] - s[i-1] 即可。
差分:一阶差分是离散函数中连续相邻两项之差,数学图像上可以直观反映其数据波动性(可能吧),在算法上就是建立记录数组每项与前一项差值的差分数组, f[i] = a[i] - a[i-1] !
具体实现方式和上述前缀和数组实现方式一样,只是公式换了下而已。那么现在再甩一个小问题题来体现以下差分的神奇之处吧!
??:数组a[1]=7,a[2]=7,a[3]=5,a[4]=8,a[5]=2,a[6]=5,a[7]=8,每次需要对 L 和 R 之间的每个数加上 num ,求修改后的数组。
@@:直接暴力O(n)修改,不出意外结果都是TLE,或许能用下线段树,O(logn)修改区间值,是的,复杂度上挤挤还是能过的,但先别急的打代码,想一想这个简单的小问题你要用多少操作才能解决:首先建一棵树(简单),其次写区间更新代码,中间还要写个lazy数组标记优化,然后才可以进行题目所需要的修改操作,可到这里还没结束,题目还需要打印数组,可你所修改的数组还在线段树的最底下,唉,从底层遍历打印数组又是一个麻烦的操作。当一个个bug出现时,你在想,为什么一开始不用差分来实现呢?(敲黑板!)我们直接对[L,R]区间进行加值操作,在f[L]处加一个k,在f[R+1]处就减去一个k。最后求序列的每个位置变成了多少,只需要求一下f数组的前缀和,然后和原数组按位相加就好。(这个结论的证明我不怎么会,呃呃......反正记得它是对的就行了,毕竟只是闲谈一下而已,具体证明应该是探究差分和前缀和之间的关系吧。)
现在抛出一道简单应用题:
链接:https://ac.nowcoder.com/acm/contest/5477/F
来源:牛客竞赛科大讯飞杯”第十七届同济大学程序设计预选赛暨高校网络友谊赛F题排列计算:
题目描述
天才程序员菜哭武和石头组队参加一个叫做国际排列计算竞赛 (International Competition of Permutation Calculation, ICPC) 的比赛,这个比赛的规则是这样的:
一个选手给出一个长度为 n 的排列,另一个选手给出 m 个询问,每次询问是一个形如 (l, r) 的数对,查询队友给出的排列中第 l 个数到第 r 个数的和,并将查询到的这个区间和加入总分,最后总分最高的队伍就能获胜。
石头手速很快,在比赛一开始就给出了 m 个询问;菜哭武也很强,他总是能找到最合适的排列,使得他们队的总分尽可能高。
在看比赛直播的你看到了石头给出的 m 个询问,聪明的你能不能预测出他们队伍最终的得分呢?
一个排列是一个长度为 n 的数列,其中 1 ~ n 中的每个数都在数列中恰好出现一次。比如 [1, 3, 2] 是一个排列,而 [2, 1, 4] 和 [1, 2, 3, 3] 不是排列。
输入描述:
第一行输入两个数 n (1≤n≤2×105) 和 m (1≤m≤2×105) 。
接下来 m 行,每行输入两个数 l 和 r ,代表这次查询排列中第 l 个到第 r 个的和。
输出描述:
输出一个整数,代表他们队伍总分的最大值。
题目分析:
由1~n这些数字随机组成的数列,进行m次查询,每次查询取该范围数字之和,试构造一种序列,使得结果最大化。数列中每个数对结果的贡献是查询数字乘以该数字, 因此,为了使结果最大化,我们不妨在查询次数大的位置放置大的数字,即可以用差分的方法统计每个位置出现的次数,然后按次数就行排序,出现次数越多,位置数字越大,最后统计一下他们的和就行了。
代码:
#include<stdio.h> #include<algorithm> using namespace std; typedef long long ll; int a[200020]; int l,r,n,m; int main() { scanf("%d%d",&n,&m); while(m--) { scanf("%d%d",&l,&r); a[l]++; a[r+1]--;//差分记得右边加1(自己模拟下就知道了) } for(int i=1;i<=n;i++) { a[i]+=a[i-1];//累加更新区间值 } sort(a+1,a+1+n); ll sum = 0;//longlong for(int i=1;i<=n;i++) { sum+=i*a[i]; } printf("%lld",sum); }
总结:
最后来总结一下差分和前缀和的用途:(下课铃敲黑板◕∀◕)
前缀和:处理区间和问题
差分:快速处理区间加减操作
好了啦,下课了o(∩_∩)o

浙公网安备 33010602011771号