【题解提供者】吴立强
解法【1】
思路
直接统计每次询问的答案 ∑ i = l i r i A i \sum_{i=l_i}^{r_i} A_i ∑i=liriAi 即可。
代码展示
#include <iostream>
using namespace std;
const int N = 1009;
int a[N];
int main() {
int n; cin >> n;
for(int i = 1; i <= n; i ++) cin >> a[i];
int m; cin >> m;
while(m --) {
int l, r; cin >> l >> r;
int sum = 0;
for(int i = l; i <= r; i ++) sum += a[i];
cout << sum << endl;
}
return 0;
}
算法分析
不难发现,上述程序的循环内所需运行次数为 n + ∑ i = 1 m ( r i − l i + 1 ) n+\sum_{i=1}^m (r_i-l_i+1) n+∑i=1m(ri−li+1),在极限数据(所有查询都是从 1 到 n)其级别为 O ( n × m ) O(n\times m) O(n×m) 可以通过 1000 的数据,而在 2 × 1 0 5 2\times 10^5 2×105 的数据下会 TLE。
解法【2】
思路
定义 s u m i sum_i sumi 为数组中前 i i i 个元素的和。
那么有 s u m i = s u m i − 1 + A i sum_i = sum_{i-1}+A_i sumi=sumi−1+Ai,特别的, s u m 0 = 0 sum_0 = 0 sum0=0。
通过线性时间处理出上述数据后,有: ∑ i = l i r i A i = s u m r i − s u m l i − 1 \sum_{i={l_i}}^{r_i}A_i = sum_{r_i} - sum_{l_i-1} ∑i=liriAi=sumri−sumli−1。
代码展示
#include <iostream>
using namespace std;
typedef long long ll; /// 类型定义(用 ll 来代替了 long long 的功能)
const int N = 200009;
int a[N];
ll sum[N]; /// 极限数据下 sum[n] 可以达到 2e10 的级别超出 int 的表达范围
int main() {
int n; cin >> n;
for(int i = 1; i <= n; i ++) {
cin >> a[i];
sum[i] = sum[i - 1] + a[i]; /// 预处理,由于是全局数组,故存在 sum[0]=0
}
int m; cin >> m;
while(m --) {
int l, r; cin >> l >> r;
cout << sum[r] - sum[l - 1] << endl; /// O(1) 回答
}
return 0;
}
算法分析
程序时间复杂度为 O ( n + m ) O(n+m) O(n+m),可以通过 2 × 1 0 5 2\times 10^5 2×105 的数据。
算法拓展
上述算法为【前缀和】算法,其可以将区间和查询从区间长度优化至两次单点查询。
这揭示了一种解题思想:区间个数是 O ( n 2 ) O(n^2) O(n2) 级别的,但是利用【前缀】这个信息可以快速维护出区间的信息,更重要的是【前缀】只有 n + 1 n+1 n+1 个(还有一个空前缀),且前缀之间存在递推关系,故通过 O ( n ) O(n) O(n) 级别的预处理后就可以加速区间查询的速度。