前缀和
例1
读入一个班n个同学的成绩,之后读入一个数q,表示q次询问,每次询问当一个同学分数为x时,他排多少名。
很明显,挨个数时间复杂度太高。只需要统计大于等于x分的人数即可得到该同学的名次。可以使用greater[i]数组维护,方式是不断加上分数大于等于i的人数。举例说明:考1、 2、 3、 4、 5分(分可真低)的人数分别为4、 2、 3、 5、 1人,那大于等于1、 2、 3、 4、 5分的人数即为a[1]=15 a[2]=11 a[3]=9 a[4]=6 a[5]=1。如果一名同学考了3分,直接用大于等于3分的人数减去等于3分的人数再+1即可(直接维护大于x分的人数好像更香QwQ,但仔细想想还是要将大于x分的人数+1才行,简便程度差不多)。
将对整个区间的查询转化为对区间端点的查询,这种思想就是前缀和的核心。要求对于区间操作尽可能往这里想。
前缀和可用于求区间和。我们还可以发现,不仅可以通过sum数组轻松求出同学的总成绩,甚至可以询问分数在[x,y]区间内的同学有多少个。方式就是用sum[x]-sum[y-1](即用大于等于x的人数减去大于等于y-1的分数的人数)。以下为经典模板:
已知数列{an},有q次询问,每次询问数列每次询问数列第li个元素到ri个元素的和。
核心代码:ans=sum[ri]-sum[li-1]; //sum[i]存前i个数的和
差分
为进一步引申出差分再举一个例子:
例2
每个同学都知道自己的分数比前一人多多少分,告诉你第一个人的分数,求每个人的分数。第x个同学记性不好,算错了他和前一个人的差值(但其他同学没算错),现在将他和第一个同学的差值改为y,求每个同学的分数。
上述例子可以抽象化为一个数列修改问题:给一个数列{an},有q次修改,每次将数列中第li到ri的每个元素都加上一个值ki,求所有修改后每个数的值。
通过上述前缀和的思想,我们思考是否可以通过维护一个量,保证可以通过操作区间端点达到目的,即维护的量可以保证在修改时区间内部量不变,区间端点量改变。易得,此量即是差值。举例来具体说明:
令a[i]是第i名同学与其前面同学的分数差值,假设第1到4名同学与之前同学的分数之差分别为a[1]=88(作为第一名同学所以分数设大点), a[2]=1, a[3]=3, a[4]=-1,a[5]=4,则易得第1到4名同学的分数为88、89、92、91、95。
这时若发现第二名同学记错了,其实他自己比前一名同学多了5分,少算了4分!我们仅修改区间端点:a[2]=5。可以发现,此时后方同学再计算(前一名同学的分数+自己与前一名同学的分数差)时,就自动加上了这4分。
区间修改也是一样的,但是对所给区间加减值,而不是像上述修改分数一样,之后的所有分数都要变化。那操作区间右方的值多加了怎么办?很简单,再减去之前多加的就好了。还是拿上面的例子说明,假设只有区间[2,3]上的值需要多加4,那么对于a[2]同样使它的值为5,使得第2、3名同学的分数变为93、 96。既然前面的改好了,只要让a[4]减去多加的就行啦,即让a[4]=-5。再计算第4、 5名同学的分数,发现为91、95没变。
总结:设delta[i]维护第i个数与前一个数的差值,则修改区间[l,r]的值使其加x,可以通过使delta[l]+x、delta[r+1]-x来维护区间内的数值。而用数组delta[i]来维护第i个数和前一个数的差值的办法叫做差分。上面例子中的a[i]其实就是差分数组。
两者的关系
可以发现差分和前缀和是一对对称的操作:对差分数组求前缀和就是原数组(分数差求前缀和就是原来的分数),对前缀和求差分也会得到原数组(对大于等于x分的人数求差分会得到原来x分数下的人数)。
令sum为前缀和数组,a为原数组,delta为差分数组,为了好理解不妨就解释sum为考试分数大于等于x的人数,a为等于x的人数,delta为等于x的人数与等于x-1人数的差值,则:
sum[i]=a[i]+sum[i-1]; // 大于等于i分的人数=i分的人数+大于等于i-1分的人数
a[i]=sum[i]-sum[i-1]; // i分的人数=大于等于i分的人数-大于等于i-1分的人数
delta[i]=a[i]-a[i-1]; // i分的人数与i-1分的人数的差值=i分的人数 - i-1分的人数
a[i]=delta[i]+a[i-1]; //i分的人数=i分的人数与i-1分的人数的差值 + i-1分的人数