604 字
3 分钟
学习笔记-4-前缀和&差分

前缀和#

定义#

定义很简单,就是一个数列中前n项的和。虽然简单,却是一种非常好用的减少时间复杂度的优化方法。

一维#

原理#

sumr=a1+a2+......+arsum_r=a_1+a_2+......+a_r

suml1=a1+a2+......+al1sum_{l-1}=a_1+a_2+......+a_{l-1}

sumrsuml1=al+al+1+...arsum_r-sum_{l-1}=a_l+a_l+1+...a_r

实现#

定义#

int sum[N];
sum[0]=a[0];
for(int i=;i<N;i++){
sum[i]=sum[i-1]+a[i];
}

查询#

//l到r区间之和
cout<<sum[r]-sum[l-1];

二维#

通过图片可知,sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j]

不难推出,s[x2, y2] - s[x1 - 1, y2] - s[x2, y1 - 1] + s[x1 - 1, y1 - 1]

差分#

差分可以理解为前缀和的逆运算,即如果一个数组a的前缀和数组为b,则数组a是数组b的差分数组。

构造差分#

最简单的构造方法:

int a[n],[n];
b[0]=a[0];
for(int i=1;i<n;i++)
{
b[i]=a[i]-a[i-1];
}

一维#

一维差分是指给定一个长度为n的序列a,要求支持操作fuc(l,r,c)表示对a[l]~a[r]区间上的每一个值都加上或减去常数c,并求修改后的序列a。

//区间[l,r]中的所有值都加上常数c
b[l] += c;
b[r+1] -= c;
//上边语句实现原理 b相当于a的辅助数组
//把a序列分为[1,l-1],[l,r],[r+1,n]三部分,由差分定义和与前缀和关系可得
a[l-1] = b[1]+b[2]+...+b[l-1]; //b[1]~b[l-1]中所有值都未改变,a[l-1]也不变
a[l] = b[1]+b[2]+...+b[l-1]+b[l]; //b[1] += c,所以a[l] += c
a[l+1] = b[1]+b[2]+...+b[l-1]+b[l]+b[l+1]; //b[1] += c,所以a[l+1] += c
... //一直到
a[r] = b[1]+b[2]+...b[l]+...+b[r]; //b[1] += c,所以a[l+1] += c
a[r+1] = b[1]+b[2]+...b[l]+...+b[r]+b[r+1]; //b[l] += c,b[r+1] -= c;所以a[r+1]不变
//所以由此可知上面的两个语句(b[l] += c;b[r+1] -= c)可以实现a数组在区间[l,r]内的所有值都加上了常数c

二维#

如果扩展到二维,我们需要让二维数组被选中的子矩阵中的每个元素的值加上c,是否也可以达到O(1)的时间复杂度。答案是可以的,考虑二维差分。

操作#

void insert(int x1,int y1,int x2,int y2,int c)
{
b[x1][y1] += c;
b[x2+1][y1] -= c;
b[x1][y2+1] -= c;
b[x2+1][y2+1] += c;
}

初始化

for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= m; j++)
{
insert(i,j,i,j,a[i][j]);
}
}

区间修改#

对于区间 [l,r][l,r],所有元素都加上一个值 cc,等价于在差分数组中 chai+=c,char+1=ccha_i+=c,cha_{r+1}-=c。 然后对这个差分数组求前缀和即可找到答案。

此文章参考了前缀和与差分 图文并茂 超详细整理(全网最通俗易懂)算法笔记(六):差分法的相关内容,在此表示感谢。

学习笔记-4-前缀和&差分
https://blog.introl.top/posts/学习笔记-4-前缀和差分/学习笔记-4-前缀和-差分/
作者
Introl
发布于
2024-05-28
许可协议
CC BY-NC-SA 4.0