前缀和
一维前缀和(区间)
- 这样的好处是,可以以
O(1)
的时间复杂度来计算。而不是遍历O(n)
。 - 当读入数据非常大(>1000000)的时候,建议使用
scanf()
来读取数据,会比cin >>
快很多。 - 在全局开数组的时候比如
A[100][100];
,a[10010]
不必手动定义下标0的元素为0,因为初始化之后所有元素的值均为0.
- 递推公式(前缀和的初始化)
读入数组a[N]之后,前缀和的初始化:
for(int i = 1; i <= n; i ++)
S[i] = S[i - 1] + a[i];
- 任意连续区间
[l, r]
的部分和
部分和
= S[r] - S[l-1]
= a[l] + a[l+1] + ... + a[r]
例如,求区间[1,10]
区间的和,就是 S[10] - S[0]
。
二维前缀和(子矩阵)
这个定义方式和循环有关。。
- 递推公式(前缀和的初始化)
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
S[i][j] = S[i-1][j] + S[i][j-1] - S[i-1][j-1] + a[i][j];
- 子矩阵元素的和(部分和)
下标为1都-1
公式:
S[x1,y1][x2,y2] = S[x2,y2] - S[x1-1,y2] - S[x2,y1-1] + S[x1-1,y1-1]
综合运用 步骤
差分
一维差分
作用:
- .对数组a[] 求一遍前缀和就可以得到数组S[]
O(n)
- 使得已知原数组
S[]
在区间[l, r]
上实现:S[l]+c, S[l+1]+c...S[r]+c
。
只需要a[l]+c; a[r+1]+c
:
注意,如果使用暴力做法,时间复杂度是 O(n)
,使用差分数组,时间复杂度是 O(1)
综合运用 步骤
-
具体的insert()函数 初始化差分数组的原理就是利用
a[n] = S[n] - S[n-1]
:
-
读入已知数组S[];利用insert()函数初始化差分数组a[];根据请求修改差分数组;之后求出修改后对应的答案数组S[],有两种方法:
1)使用递推公式修改原数组S[]
for(int i = 1; i <= n; i ++)
S[i] = S[i-1] + a[i];
2)直接把差分数组改成答案数组
好吧,就像是直接拿a[]空间来储存S[]的样子…
for(int i = 1; i <= n; i ++)
a[i] = a[i-1] + a[i]
<##>
一般前缀和的实际用处<##>
- 关于字符串,需要使用
str[N]
来读入,注意读入差分数组是从下标1开始存入的,所以要scanf("%d", str+1);
并且这个时候就不要使用取地址符号&
了。 - 除了O(n)计算区间的部分和,前缀和还可以统计一段字符串中某个字符a出现的次数。初始化字符串之前,字符== a;那么赋值为1,否则为0。这个时候一般要开二维数组
A[i][j]
,使用i来区分不同的数组,然后str[j] - 'A' / str[j] - 'a'
来进行比较…再初始化前缀和数组。 - 有的题目还是根据题干信息进行寻找潜在的前缀和雏形。
二维差分(差分矩阵)
1)让a[]的子矩阵范围(x1,y1->x2,y2)内的每一个数据都加+c,只需要处理差分矩阵的四个元素即可。
2)差分矩阵开始的初始化也是通过insert()函数来实现。
3)最后求出修改后的答案数组,就是求一下修改后的a[]的前缀和就好。
关于思路解释
一开始假设前缀和数组S[]中的元素都是0,那么对应的差分数组a[]中元素也都是0—这就满足了前缀和数组恶和差分数组的对应关系。insert()
函数实现的功能是使S[]特定区间上的元素S[i]都+c,使用for循环逐个insert(i,i,S[i])
,逐一插入S[i]那么就构造出了已知的前缀和数组S[]。至于insert()内部对于差分数组a[]的改动,是基于可以保持a[]是S[]的差分数组这一条件下的,所以在构建完S[]数组的同时a[]也完成了构建。