一维前缀和
食用指南:
对该算法程序编写以及踩坑点很熟悉的同学可以直接跳转到代码模板查看完整代码
只有基础算法的题目会有关于该算法的原理,实现步骤,代码注意点,代码模板,代码误区的讲解
非基础算法的题目只有题目分析,代码实现,代码误区
从这篇算法开始,精炼了算法内容,减少了冗余解释
题目描述:
-
输入一个长度为 n 的整数序列。
接下来再输入 m 个询问,每个询问输入一对 l,r。
对于每个询问,输出原序列中从第 l 个数到第 r 个数的和。输入格式
第一行包含两个整数 n 和 m。
第二行包含 n 个整数,表示整数数列。
接下来 m 行,每行包含两个整数 l 和 r,表示一个询问的区间范围。输出格式
共 m 行,每行输出一个询问的结果。数据范围
1≤l≤r≤n,
1≤n,m≤100000,
−1000≤数列中元素的值≤1000
输入样例:
5 3
2 1 3 6 4
1 2
1 3
2 4
输出样例:
3
6
10 -
题目来源:https://www.acwing.com/problem/content/797/
题目分析:
- 逻辑很简单,求arr[l] 到 arr[r] 的数组元素和
- 数据量:n = 十万,m = 十万
- 暴力:
m次询问,每次从arr[l]累加到arr[r]
时间复杂度最坏为O(mn),每次l到r都从arr[0]到arr[n-1]
十万的平方超过了1亿,只能过部分测试点 - 一维前缀和:
提前打表,记录前x个数的和s[x],每次查询l到r的数之和只需要s[r] - s[l-1]
时间复杂度O(n),打表花费时间,查表不耗时
可以通过所有测试点
算法原理:
含义:
- 建立一个前缀和数组s[]:s[i]表示从arr[1]到arr[i]的i个数之和
作用:
- 一次打表,多次查询,提升速度:
打表耗时O(n),查表耗时O(1) - 求区间和:
一维前缀和公式:s[j] - s[i-1] == arr[i] 到 arr[j] 累加之和
- 前缀和叫前缀和,但是其实是用来求任意区间内数组元素之和的
存储形式:
- 数组即可,但是s[0]和arr[0]都不放元素,设置为0
换句话说,前缀和数组s[] 和 数据数组arr[] 都从1开始存储
写作步骤:
打表:
- s[i] = s[i-1] + arr[i];
- s[0] = 0;
查表:
- s[j] - s[i-1] == arr[i] 到 arr[j]的累加之和
代码模板:
#include <iostream>
using namespace std;
int N = 100010;
//s和arr数组放在main()外,默认arr[0]==s[0]==0
int s[N];
int arr[N];
int main(){
int n=0, m=0;
cin >> n >>m;
//注意点1:arr[] 和 s[] 数组都是从1开始存储的;这样方便求s[i]
for(int i=1; i<=n; i++){
//注意点2:循环输入同时完成打表
cin >> arr[i];
s[i] = s[i-1] + arr[i];
}
//查表
for(int i=0; i<m; i++){
int l = 0, r = 0;
cin >>l >>r;
cout<< s[r] - s[l-1] <<endl;
}
}
代码误区:
1. 区间和公式为什么是s[j] - s[i-1] 而不是 s[j] - s[i]?
- 从定义出发:
s[j] 表示 arr[1] + … + arr[j]
s[i-1] 表示 arr[1] + … + arr[i-1]
做差:arr[i] + arr[i+1] + … + arr[j],是从i累加到j
若写作s[j] - s[i] ,则是从i+1累加到j
2. 可不可以arr和s都从0存储:
- 可以,但是此时的s[i]计算公式需要分类讨论了
当i == 0时, s[i] = arr[i]
当i > 0时,s[i] = s[i-1] + arr[i];
代码整体性不高
不如(A[i] - t - B[i] + 10) % 10; 和 ((A[i] - t -B[i])%10 + 10) % 10的完成度高
不记得这两个公式是什么的,可以看看原来的大数减法博客,传送门
本篇感想:
- 前缀和就是用来求区间和的,但是叫多了从字面上看不出这个功能,所以我推荐叫他求区间和的一维前缀和
- 精简了体系之后写起来快多了,相信你们读起来也快多了
- 刚刚更新完大数除法后有今天摸鱼的想法,这是邪恶的,继续写博客吧
- 看完本篇博客,恭喜已登《练气境-初期》
距离登仙境不远了,加油