算法基础:前缀和差分

问题引入:

给n个数a1 a2 a3 . an,q次询问,每次问你区间[L,R]数之和为多少.

解法1:暴力计算

最差复杂度:O(q*n)

当n > 1e4,q > 1e4的时候,代入发现

nq > 1e8,1秒之内跑不完.

这就要求我们寻找更优的算法.

前缀和:

第一步:预处理.一次循环求出序列的每一个前缀的和。用一个数组S存,S[i]代表下标从1~i的序列和

例如:对于序列a: 1 3 2 4(有四个前缀)

S[1] = 1,S[2] = 1+3=4;

S[3] = 1+3+2=6,S[4] = 1+3+2+4=10;

我们发现递推式:S[i] = S[i-1]+a[i]

第二步:查询区间和[L,R]

因为我们知道[1~R]的区间和等于

[1~L-1]的和 加上 [L ~ R] 的和.

即S[1~R] = S[1~L-1] + S[L~R]

所以区间[L,R]的和为:

S[L~R] = S[R] - S[L-1]

给一个长度为n的只含小写字母的字符串,q次询问,每次问你子串[L,R]之间,有多少个小写字母a

第一步:预处理.用一个数组S,一次循环求出每个前缀的a的个数。S[i]代表下标从1~i的前缀中a的个数.S[i] = ?

S[i] = S[i-1] + (str[i - 1] == ‘a’)

第二步:查询[L,R]之间有多少个a;

S[R] - S[L - 1]

思考合理性:

一句话说明:一个区间里a出现的次数 等于 将这个区间划分成若干个小区间,每个小区间里a出现的次数之和.

S[1~R] = S[1~L-1] + S[L ~ R]

所以:S[L~R] = S[1~R] - S[1~L-1].

再思考一个问题:前缀和是否能查询区间最大值.

ST表解决.(原理:倍增,动态规划)

二维前缀

问题引入:给你一个数字矩阵,给你q次询问,每次问(x1,y1)到右下角(x2,y2)所构成的子矩阵数字之和为多少 .

注:左上角为(1,1)

例如: 1 1 1 1

2 2 2 2

3 3 3 3

2 2 3 4 => 2+2+3+3 = 10.

暴力解决:最差复杂度O(n^3).

第一步:预处理.用一个二维数组S

S[i][j]代表 矩阵下标从1,1 累加到 i,j的前缀子矩阵和. — 找找状态转移

S[i][j] = S[i - 1][j] + S[i][j - 1] - S[i-1][j-1] + a[i][j]
算法基础:前缀和差分
第二步:查询区间和(从x1y1到x2y2)
算法基础:前缀和差分

差分:(前缀和的逆过程)

问题引入:给你n个数a1 a2 a3 … an.

m次操作:每次给区间[L,R]上的每个数 + c.

问你m次操作之后,每个数最终为多少.输出这个序列

方法①暴力模拟:复杂度O(n^2),太慢,不建议使用。

方法②差分:

先求差分数组d:d[i] = a[i] - a[i-1].(a为原数组)

例如: 原数组a: 1 3 2 4

差分数组d:1 2 -1 2 -4

注意:

①得到的差分数组d[1] = a[1](相当于第一个数减0)

②差分数组最后比原数组多一个数(相当于0减最后一个数)

性质1:

对差分数组求前缀和得到原数组:

s[i] = d[1] + d[2] + … + d[i] = (a[1] - 0) + (a[2] - a[1]) + (a[3] - a[2]) + … + (a[i] - a[i-1]) = a[i]

再对s[i]作前缀和得到原序列的前缀和。

性质2:对差分数组d[L] += c,d[R+1]-=c 等效于原序列区间[L,R]所有值+c.

①d[R+1] = a[R+1] - a[R] - c,

②d[L] = a[L] - a[L-1] + c

解释:假设原数组[L,R]都+c,那么对于差分数组来说,就只有d[R+1] 与 d[L]这两个值要变,其他位置的差分值都不变(因为原数组的相对大小不变)

应用性质1(从L-1开始推):

③S[L-1] = a[L-1]

S[L] = S[L-1] + d[L] => S[L] = a[L] + c(将②③代入)

S[L+1] = S[L] + d[L+1] => S[L+1] = a[L+1] + c

…(c一直往下传递,直到碰到d[R+1],将这个c抵消)

这样,我们就成功的在O(1)时间内将区间[L,R]上所有值都+c.m次操作复杂度O(m),最后O(n)时间内可以得到原数组(将差分数组求前缀和)

回到刚才那个题:

第一步:读入n个数,用a数组装着

第二步:预处理求出a数组对应的差分数组d.

第三步:对于每次操作,给出L,R.

使d[L] += c,d[R+1]-=c

第四步:得到最终d数组之后,求一遍前缀和,获得原数组,输出

思考一个问题:如果修改操作和查询操作混合在一起

,差分的思想是否还派得上用场呢?

*线段树,树状数组解决

总结:

①前缀和与差分 实际上是一对逆运算

②差分前缀和实际上是一种预处理技术,不支持修改.若要修改,需要使用更高级的数据结构。