前缀和
前缀和可以理解为数列的前 n 项的和。它通过预处理的方式,能够快速查询序列中从第L个数到到第R个数的和。
算法思想
其基本思想是在原序列的基础上预处理一个前缀和数组 s [ ] s[] s[],其中 s [ i ] s[i] s[i]表示序列前 i i i个数的和。通过前缀和就可以求出序列中任意区间中所有元素的和。
例如,对于序列 A = ( 2 , 3 , 5 , 7 , 1 , 3 , 4 , 8 , 5 , 2 ) A=(2,3,5,7,1,3,4,8,5,2) A=(2,3,5,7,1,3,4,8,5,2):
- s [ 2 ] s[2] s[2]表示序列前2个数的和,为 2 + 3 = 5 2+3=5 2+3=5;
- s [ 6 ] s[6] s[6]表示序列前6个数的和,为 2 + 3 + 5 + 7 + 1 + 3 = 21 2+3+5+7+1+3=21 2+3+5+7+1+3=21;
构造前缀和数组
前缀和可以通过递推的方式从原数列中求出:
s
[
i
]
=
s
[
i
−
1
]
+
A
[
i
]
s[i] = s[i - 1] + A[i]
s[i]=s[i−1]+A[i]。其中
s
[
i
−
1
]
s[i - 1]
s[i−1]表示序列中前
i
−
1
i - 1
i−1个数的和。序列
A
A
A的前缀和数组如下图所示:
求区间和
预处理得到前缀和后,可以快速计算序列中一段连续区间
[
L
,
R
]
[L,R]
[L,R]所有元素的和:
s
[
R
]
−
s
[
L
−
1
]
s[R]-s[L - 1]
s[R]−s[L−1]。例如序列
A
A
A中区间
[
3
,
6
]
[3,6]
[3,6]所有数的和为
s
[
6
]
−
s
[
2
]
=
16
s[6]-s[2]=16
s[6]−s[2]=16
时间复杂度
前缀和算法的时间复杂度分为两部分:
- 构造前缀和数组: O ( n ) O(n) O(n)
- 求区间和: O ( 1 ) O(1) O(1)
代码模板
//构造前缀和
for(int i = 1; i <= n; i ++)
{
s[i] = s[i - 1] + a[i];
}
//计算区间[L,R]所有数的和
cout << s[R] - s[L - 1];
真题演练
题目链接:NOI Online #3 提高组
有 n n n 个容量无穷大的水壶,它们从 1 ∼ n 1\sim n 1∼n 编号,初始时 i i i 号水壶中装有 A i A_i Ai 单位的水。
你可以进行不超过 k k k 次操作,每次操作需要选择一个满足 1 ≤ x ≤ n − 1 1\le x\le n-1 1≤x≤n−1 的编号 x x x,然后把 x x x 号水壶中的水全部倒入 x + 1 x+1 x+1 号水壶中。
最后你可以任意选择恰好一个水壶,并喝掉水壶中所有的水。现在请你求出,你最多能喝到多少单位的水。
输入格式
第一行一个正整数 n n n,表示水壶的个数。
第二行一个非负整数 k k k,表示操作次数上限。
第三行 n n n 个非负整数,相邻两个数用空格隔开,表示水壶的初始装水量 A 1 A_1 A1, A 2 A_2 A2, ⋯ \cdots ⋯, A n A_n An。
输出格式
一行,仅一个非负整数,表示答案。
样例输入
10
5
890 965 256 419 296 987 45 676 976 742
样例输出
3813
数据规模与约定
- 对于 10 % 10\% 10% 的数据,保证 n ≤ 10 n \leq 10 n≤10。
- 对于 30 % 30\% 30% 的数据,保证 n ≤ 100 n \leq 100 n≤100。
- 对于 50 % 50\% 50% 的数据,保证 n ≤ 1 0 3 n \leq 10^3 n≤103。
- 对于 70 % 70\% 70% 的数据,保证 n ≤ 1 0 5 n \leq 10^5 n≤105。
- 对于 100 % 100\% 100% 的数据,保证 1 ≤ n ≤ 1 0 6 1\leq n\leq 10^6 1≤n≤106, 0 ≤ k ≤ n − 1 0\leq k \leq n-1 0≤k≤n−1, 0 ≤ A i ≤ 1 0 3 0\le A_i\le 10^3 0≤Ai≤103。
解题思路
根据题目描述每次操作把 x x x 号水壶中的水全部倒入 x + 1 x+1 x+1 号水壶,并且最后只能选择一个水壶,求最多能喝到多少单位的水。那么可以的下面的性质:
- 选择一段编号连续的水壶倒水才能避免浪费。可以进行 k k k次操作,那么这段连续区间的长度为 k + 1 k+1 k+1。
- 最多能喝到多少单位的水,其实就是求连续区间 [ i , i + k ] [i,i +k] [i,i+k]和的最大值,可以使用前缀和求解。
代码实现
#include <iostream>
using namespace std;
const int N = 1e6 + 10;
int s[N];
int main()
{
int n, k;
scanf("%d%d", &n, &k);
for(int i = 1; i <= n; i ++) {
scanf("%d", s + i);
s[i] = s[i - 1] + s[i];
}
int ans = 0;
for(int i = 1; i + k <= n; i ++) {
ans = max(ans, s[i + k] - s[i - 1]);
}
cout << ans << endl;
}
总结
- 通过对序列构造前缀和可以快速插叙序列中一段连续区间的和。
- 前缀和 s [ i ] s[i] s[i]表示序列前 i i i个数的和, s [ i ] = s [ i − 1 ] + A [ i ] s[i] = s[i - 1] + A[i] s[i]=s[i−1]+A[i]。
- 序列A中从第L个数到到第R个数的和= s [ R ] − s [ L − 1 ] s[R]-s[L - 1] s[R]−s[L−1]