前缀和、差分简析

前缀和

前置知识: ∑ i = 1 r a i = a l + a l + 1 + ⋯ + a r − 1 + a r \sum_{i = 1}^{r}{a_i} = a_l + a_{l + 1} + \dots + a_{r - 1} + a_r i=1rai=al+al+1++ar1+ar
考虑以下数组:

i i i 1 1 1 2 2 2 3 3 3
a I a_I aI 3 3 3 5 5 5 7 7 7

如果我们要查询 ∑ i = 1 2 a i \sum_{i = 1}^{2}{a_i} i=12ai,很显然可以得到 ∑ i = 1 2 a i = 3 + 5 = 8 \sum_{i = 1}^{2}{a_i} = 3 + 5 = 8 i=12ai=3+5=8。如果我们要查询 ∑ i = l r a i \sum_{i = l}^{r}{a_i} i=lrai,则每次的时间复杂度为 O ( r − l ) O(r -l) O(rl)
如果我们对于一个长度为 n n n 的数组查询 k k k 次(不修改),则时间复杂度为 O ( n k ) O(nk) O(nk) 。如果给定一个 n = 1 0 7 , k = 1 0 6 n = 10^7,k = 10^6 n=107,k=106,我们要如何让程序在 1s 内运行完呢?

i i i 1 1 1 2 2 2 3 3 3
a I a_I aI 3 3 3 5 5 5 7 7 7
b I b_I bI 3 3 3 8 8 8 15 15 15

我们构造 b 0 = 0 , b i = b i − 1 + a i b_0 = 0,b_i = b_{i - 1} + a_i b0=0,bi=bi1+ai,同上表,倘若我们只考虑快速查询 ∑ i = 1 r a i \sum_{i = 1}^{r}{a_i} i=1rai,显而易见原式即为 ∑ i = 1 r a i = b i \sum_{i = 1}^{r}{a_i} = b_i i=1rai=bi,而 b b b 数组可以在 O ( n ) O(n) O(n) 的时间范围内预处理完。简易证明见下:

b i = b i − 1 + a i = b i − 2 + a i − 1 + a i = ∑ i = 1 r a i b_i = b_{i - 1} + a_i = b_{i - 2} + a_{i - 1} + a_i = \sum_{i = 1}^{r}{a_i} bi=bi1+ai=bi2+ai1+ai=i=1rai

假如我们转换一下思路,可以发现 ∑ i = l r a i = ∑ i = 1 r a i − ∑ i = 1 l − 1 a i = b r − b l − 1 \sum_{i = l}^{r}{a_i} = \sum_{i = 1}^{r}{a_i} - \sum_{i = 1}^{l-1}{a_i} = b_r - b_{l - 1} i=lrai=i=1raii=1l1ai=brbl1,那么我们将单次查询复杂度降到了 O ( 1 ) O(1) O(1),总时间复杂度变为 O ( n + k ) O(n + k) O(n+k)
在这里插入图片描述
例题

#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
#include<queue>
#include<map>
#include<algorithm>

using namespace std;

// 快读
inline int read() {
	int x = 0,f = 1;char ch = getchar();
	while (ch < '0' or ch > '9'){if (ch == '-') f = -1;ch = getchar();}
	while (ch >= '0' and ch <='9'){x = x * 10 + ch - 48;ch = getchar();}
	return x * f;
}
//快写
inline void write(int x) {
	if(x < 0) putchar('-'),x = -x;
	if(x > 9) write(x / 10);
	putchar(x % 10 + '0');
	return;
}
//输出char
void writec(char x) {
	putchar(x);
	return;
}
int main() {
	int n = read(),a[100005],m;
	for(int i = 1;i <= n;i++) a[i] = read() + a[i - 1];
	m = read();
	while(m--) {
		int l = read() - 1,r = read();
		write(a[r] - a[l]),writec('\n');
	}
    return 0;
}


差分

考虑完不修改多次查询,如果我们多次将区间内的所有数加上一个值,最后统一查询,那么会怎么样呢?
假设我们操作2次,第一次让 a 1 … a 3 ← ( a 1 + 3 ) … ( a 3 + 3 ) a_1 \dots a_3 \gets(a_1 + 3)\dots (a_3+3) a1a3(a1+3)(a3+3),第二次让 a 1 , a 2 ← ( a 1 + 2 ) … ( a 2 + 2 ) a_1 ,a_2 \gets(a_1 + 2)\dots (a_2+2) a1a2(a1+2)(a2+2)

i i i 1 1 1 2 2 2 3 3 3
原数组 3 3 3 5 5 5 7 7 7
操作一次 6 6 6 8 8 8 10 10 10
操作两次 8 8 8 10 10 10 10 10 10

如果你对这一些规律不太敏感的话,可以考虑 b i = a i − a i − 1 b_i = a_i - a_{i - 1} bi=aiai1,那么:

i i i 1 1 1 2 2 2 3 3 3
b i b_i bi数组 3 3 3 2 2 2 2 2 2
操作一次 6 6 6 2 2 2 2 2 2
操作两次 8 8 8 2 2 2 0 0 0

显而易见,如果区间加减, b i b_i bi 只会改变两个值,切规律如下:

b l ← b l + k , b r + 1 ← b r + 1 − k b_l \gets b_l + k,b_{r + 1} \gets b_{r + 1}- k blbl+k,br+1br+1k

我们将每次修改由 O ( n ) O(n) O(n) 变成了 O ( 1 ) O(1) O(1)

在这里插入图片描述
例题

代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
#include<queue>
#include<map>

using namespace std;
int x[10005];
// 快读
inline int read()
{
	int x = 0,f = 1;char ch = getchar();
	while (ch < '0' or ch > '9'){if (ch == '-') f = -1;ch = getchar();}
	while (ch >= '0' and ch<='9'){x = x * 10 + ch - 48;ch = getchar();}
	return x * f;
}
//快写
inline void write(int x) {
	if(x < 0) putchar('-'),x = -x;
	if(x > 9) write(x / 10);
	putchar(x % 10 + '0');
	return;
}
//输出char
void writec(char x) {
	putchar(x);
	return;
}
int main() {
	int t = read();
	while(t--) {
		int n = read(),u = read();
		for(int i = 0;i < n;i++) x[i] = 0;
		while(u--) {
			int l = read(),r = read(),val = read();
			x[l] += val;
			x[r + 1] -= val;
		}
		for(int i = 1;i < n;i++) x[i] += x[i - 1];
		int q = read();
		while(q--) {
			int i = read();
			write(x[i]),writec('\n');
		}
	}
    return 0;
}

  • 34
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值