前缀和与差分

文章介绍了前缀和的概念及其在处理多次查询和少量修改问题中的高效性,详细阐述了一维前缀和的理论实现和程序代码,以及如何进行查询操作。同时,讨论了差分方法在处理多次修改和少量查询场景的优势,包括其修改操作的时间复杂度和前缀和计算的特性。
摘要由CSDN通过智能技术生成

前缀和

用途

多用于少量修改,多次查询的问题场景中,单词查询的时间复杂度是 O ( 1 ) \mathcal O(1) O(1) 的,但是如果修改了一个元素,那么这个元素后面的前缀和都将受到影响,最劣情况下需要遍历整个前缀和数组,时间复杂度是 O ( n ) \mathcal O(n) O(n) 的。在这片博文中,我们用 a a a 代表原数组,用 s s s 代表前缀和数组。

由于 s 0 s_0 s0 是特殊定义为 0 0 0 的,所以我们将下标从 1 1 1 可以计算。

一维前缀和

理论实现

因为元素 s i s_i si 的值是 ∑ x = 1 i a i \sum\limits_{x=1}^{i} a_i x=1iai,所以,我们可以求 s i s_i si 时遍历 a 1 ∼ a i a_1\sim a_i a1ai,这样就可以得到 s i s_i si 的值。时间复杂度是 O ( n 2 ) \mathcal O(n^2) O(n2) 的。虽然这种做法对于小范围的数据可以通过,但是一旦 n n n 达到了 1 0 5 10^5 105,这种方法是承受不住的,所以我们需要更加快的方法。

思考一下,我们需要遍历 a 1 ∼ a i − 1 a_1\sim a_{i-1} a1ai1 吗?我们可以发现, ∑ x = 1 i − 1 a i \sum\limits_{x=1}^{i-1} a_i x=1i1ai 的值已经存储在 s i − 1 s_{i-1} si1 里了!所以,我们无需遍历 a 1 ∼ a i − 1 a_1\sim a_{i-1} a1ai1。所以,一维前缀和的通项公式是: s i ← s i − 1 + a i s_i\gets s_{i-1}+a_i sisi1+ai

程序实现

清楚了一维前缀和的理论实现,程序实现也不难了。

// 一维前缀和程序实现
const int MAXN = 1e5 + 10;
int N, a[MAXN], s[MAXN]; // N是数组的长度
int main() {
	cin >> N;
	for (int i = 1; i <= N; i++)
		cin >> a[i], s[i] = s[i - 1] + a[i];
	return 0;
}

经过这份程序,我们就已经将 a a a 数组的前缀和存储在 s s s 数组中了。

查询

查到头

这种方法的意思是查找 ∑ x = 1 x a x \sum\limits_{x=1}^{x}a_x x=1xax,就是要求 a 1 ∼ a i a_1\sim a_i a1ai 的和。这样是最简单的,只需输出 s i s_i si 即可,因为 s i s_i si 存储的就是这个值。

程序实现

int index;
cin >> index;
cout << s[index] << endl;
中间字串查找

这种方法的意思是查找 ∑ x = l r a x \sum\limits_{x=l}^{r}a_x x=lrax,就是求 a l ∼ a r a_l\sim a_r alar 的和。这一点需要注意的是,答案是 a r − a l − 1 a_r-a_{l-1} aral1!这很容易证明,如果答案是 a r − a l − 1 a_r-a_{l-1} aral1,那么求的是 ( l , r ] (l,r] (l,r] 这一区间的和,并不包括 a l a_l al,所以这是错误的。

程序实现

int l, r;
cin >> l >> r;
cout << s[r] - s[l - 1] << endl;

差分

用途

多用于多次修改,少量查询的问题场景中,单词修改的时间复杂度是 O ( 1 ) \mathcal O(1) O(1) 的,但是如果要查询一个元素,那么我们就需要计算一遍整个差分数组的前缀和,时间复杂度是 O ( n ) \mathcal O(n) O(n) 的。在这片博文中,我们用 a a a 代表原数组,用 b b b 代表前缀和数组。

由于 b 0 b_0 b0 是特殊定义为 0 0 0 的,所以我们将下标从 1 1 1 可以计算。

二维差分不会写,所以就不放了

理论实现

我们可以定 b 1 ← a 1 b_1\gets a_1 b1a1 b i ← a i − a i − 1 ( 2 ≤ i ≤ n ) b_i\gets a_i-a_{i-1}(2\le i\le n) biaiai1(2in)。例如:
i 1 2 3 4 5 6 7 8 a i 1 1 2 2 1 3 3 5 b i 1 0 1 0 − 1 2 0 2 \begin{array}{|c|c|c|c|c|c|c|c|c|} \hline i & 1 & 2 & 3 & 4 & 5 & 6 & 7 & 8\\\hline a_i&1&1&2&2&1&3&3&5\\\hline b_i&1&0&1&0&-1&2&0&2\\\hline \end{array} iaibi111210321420511632730852
对数组 b b b 求前缀和:
∑ i = 1 n b i = a 1 + ∑ i = 2 n ( a i − a i − 1 ) = ∑ i = 1 n a i − ∑ i = 1 n − 1 a i = a n \sum\limits_{i=1}^{n}b_i=a_1+\sum\limits_{i=2}^{n}(a_i-a_{i-1})=\sum\limits_{i=1}^{n}a_i-\sum\limits_{i=1}^{n-1}a_i=a_n i=1nbi=a1+i=2n(aiai1)=i=1naii=1n1ai=an
可以发现, b b b 的前缀和数组 s s s s i s_i si 恰好等于 a i a_i ai
若将 b x b_x bx 增加 1 1 1,再对 b b b 数组求前缀和得到 a a a 数组,那么得到的 a a a 数组中的 a x , a x + 1 , ⋯   , a n a_x,a_{x+1},\cdots,a_n ax,ax+1,,an 均增加 1 1 1。若将 b y b_y by 减少 1 1 1,再对 b b b 数组求前缀和得到 a a a 数组,那么得到的 a a a 数组中 a y , a y + 1 , ⋯   , a n a_y,a_{y+1},\cdots,a_n ay,ay+1,,an 均减少 1 1 1

例如,对一个全为 0 0 0 的差分数组 b b b,将 b 3 b_3 b3 增加 1 1 1,将 b 6 b_6 b6 减少 1 1 1,得到的 a a a 数组如表所示:
i 1 2 3 4 5 6 7 8 b i 0 0 + 1 0 0 − 1 0 0 a i 0 0 1 1 1 0 0 0 \begin{array}{|c|c|c|c|c|c|c|c|c|} \hline i&1&2&3&4&5&6&7&8\\\hline b_i&0&0&+1&0&0&-1&0&0\\\hline a_i&0&0&1&1&1&0&0&0\\\hline \end{array} ibiai1002003+11401501610700800

因此,将 a a a 数组中第 x x x 到第 y y y 个元素增加 z z z 这样的修改操作,可以转化为将 b x b_x bx 增加 z z z,将 b y + 1 b_{y+1} by+1 减少 z z z,最后再对 b b b 数组求前缀和。这样,对于每次修改操作,只需要修改两个值,时间复杂度也就是 O ( 1 ) \mathcal O(1) O(1) 的了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三日连珠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值