# 58. 区间和 > 本题为代码随想录后续扩充题目,还没有视频讲解,顺便让大家练习一下ACM输入输出模式(笔试面试必备) [题目链接](https://kamacoder.com/problempage.php?pid=1070) 题目描述 给定一个整数数组 Array,请计算该数组在每个指定区间内元素的总和。 输入描述 第一行输入为整数数组 Array 的长度 n,接下来 n 行,每行一个整数,表示数组的元素。随后的输入为需要计算总和的区间,直至文件结束。 输出描述 输出每个指定区间内元素的总和。 输入示例 ``` 5 1 2 3 4 5 0 1 1 3 ``` 输出示例 ``` 3 9 ``` 数据范围: 0 < n <= 100000 ## 思路 本题我们来讲解 数组 上常用的解题技巧:前缀和 首先来看本题,我们最直观的想法是什么? 那就是给一个区间,然后 把这个区间的和都累加一遍不就得了,是一道简单不能再简单的题目。 代码如下: ```CPP #include #include using namespace std; int main() { int n, a, b; cin >> n; vector vec(n); for (int i = 0; i < n; i++) cin >> vec[i]; while (cin >> a >> b) { int sum = 0; // 累加区间 a 到 b 的和 for (int i = a; i <= b; i++) sum += vec[i]; cout << sum << endl; } } ``` 代码一提交,发现超时了..... 我在制作本题的时候,特别制作了大数据量查询,卡的就是这种暴力解法。 来举一个极端的例子,如果我查询m次,每次查询的范围都是从0 到 n - 1 那么该算法的时间复杂度是 O(n * m) m 是查询的次数 如果查询次数非常大的话,这个时间复杂度也是非常大的。 接下来我们来引入前缀和,看看前缀和如何解决这个问题。 前缀和的思想是重复利用计算过的子数组之和,从而降低区间查询需要累加计算的次数。 **前缀和 在涉及计算区间和的问题时非常有用**! 前缀和的思路其实很简单,我给大家举个例子很容易就懂了。 例如,我们要统计 vec[i] 这个数组上的区间和。 我们先做累加,即 p[i] 表示 下标 0 到 i 的 vec[i] 累加 之和。 如图: ![](https://file1.kamacoder.com/i/algo/20240627110604.png) 如果,我们想统计,在vec数组上 下标 2 到下标 5 之间的累加和,那是不是就用 p[5] - p[1] 就可以了。 为什么呢? `p[1] = vec[0] + vec[1];` `p[5] = vec[0] + vec[1] + vec[2] + vec[3] + vec[4] + vec[5];` `p[5] - p[1] = vec[2] + vec[3] + vec[4] + vec[5];` 这不就是我们要求的 下标 2 到下标 5 之间的累加和吗。 如图所示: ![](https://file1.kamacoder.com/i/algo/20240627111319.png) `p[5] - p[1]` 就是 红色部分的区间和。 而 p 数组是我们之前就计算好的累加和,所以后面每次求区间和的之后 我们只需要 O(1) 的操作。 **特别注意**: 在使用前缀和求解的时候,要特别注意 求解区间。 如上图,如果我们要求 区间下标 [2, 5] 的区间和,那么应该是 p[5] - p[1],而不是 p[5] - p[2]。 **很多录友在使用前缀和的时候,分不清前缀和的区间,建议画一画图,模拟一下 思路会更清晰**。 本题C++代码如下: ```CPP #include #include using namespace std; int main() { int n, a, b; cin >> n; vector vec(n); vector p(n); int presum = 0; for (int i = 0; i < n; i++) { cin >> vec[i]; presum += vec[i]; p[i] = presum; } while (cin >> a >> b) { int sum; if (a == 0) sum = p[b]; else sum = p[b] - p[a - 1]; cout << sum << endl; } } ``` C++ 代码 面对大量数据 读取 输出操作,最好用scanf 和 printf,耗时会小很多: ```CPP #include #include using namespace std; int main() { int n, a, b; cin >> n; vector vec(n); vector p(n); int presum = 0; for (int i = 0; i < n; i++) { scanf("%d", &vec[i]); presum += vec[i]; p[i] = presum; } while (~scanf("%d%d", &a, &b)) { int sum; if (a == 0) sum = p[b]; else sum = p[b] - p[a - 1]; printf("%d\n", sum); } } ``` ## 其他语言版本 ### Java ```Java import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int n = scanner.nextInt(); int[] vec = new int[n]; int[] p = new int[n]; int presum = 0; for (int i = 0; i < n; i++) { vec[i] = scanner.nextInt(); presum += vec[i]; p[i] = presum; } while (scanner.hasNextInt()) { int a = scanner.nextInt(); int b = scanner.nextInt(); int sum; if (a == 0) { sum = p[b]; } else { sum = p[b] - p[a - 1]; } System.out.println(sum); } scanner.close(); } } ``` ### Python ```python import sys input = sys.stdin.read def main(): data = input().split() index = 0 n = int(data[index]) index += 1 vec = [] for i in range(n): vec.append(int(data[index + i])) index += n p = [0] * n presum = 0 for i in range(n): presum += vec[i] p[i] = presum results = [] while index < len(data): a = int(data[index]) b = int(data[index + 1]) index += 2 if a == 0: sum_value = p[b] else: sum_value = p[b] - p[a - 1] results.append(sum_value) for result in results: print(result) if __name__ == "__main__": main() ``` ### JavaScript ``` JavaScript function prefixSum() { const readline = require('readline'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); let inputLines = []; rl.on('line', (line) => { inputLines.push(line.trim()); }); rl.on('close', () => { // 读取项数 n const n = parseInt(inputLines[0]); // 使用前缀和,复杂度控制在 O(1) let sum = new Array(n); sum[0] = parseInt(inputLines[1]); // 计算前缀和数组 for (let i = 1; i < n; i++) { let value = parseInt(inputLines[i + 1]); sum[i] = sum[i - 1] + value; } // 处理区间和查询 for (let i = n + 1; i < inputLines.length; i++) { let [left, right] = inputLines[i].split(' ').map(Number); if (left === 0) { console.log(sum[right]); } else { console.log(sum[right] - sum[left - 1]); } } }); } ``` ### C ```C #include #include int main(int argc, char *argv[]) { int num; // 读取数组长度 scanf("%d", &num); // 使用动态内存分配而不是静态数组,以适应不同的输入大小 int *a = (int *)malloc((num + 1) * sizeof(int)); // 初始化前缀和数组的第一个元素为0 a[0] = 0; // 读取数组元素并计算前缀和 for (int i = 1; i <= num; i++) { int mm; scanf("%d", &mm); // 累加前缀和 a[i] = a[i - 1] + mm; } int m, n; // 循环读取区间并计算区间和,直到输入结束 // scanf()返回成功匹配和赋值的个数,到达文件末尾则返回 EOF while (scanf("%d%d", &m, &n) == 2) { // 输出区间和,注意区间是左闭右开,因此a[n+1]是包含n的元素的前缀和 printf("%d\n", a[n+1] - a[m]); } // 释放之前分配的内存 free(a); return 0; } ``` ### Go ```go package main import ( "fmt" "bufio" "strconv" "os" ) func main() { // bufio中读取数据的接口,因为数据卡的比较严,导致使用fmt.Scan会超时 scanner := bufio.NewScanner(os.Stdin) // 获取数组大小 scanner.Scan() n, _ := strconv.Atoi(scanner.Text()) // 获取数组元素的同时计算前缀和,一般建议切片开大一点防止各种越界问题 arr := make([]int, n + 1) for i := 0; i < n; i++ { scanner.Scan() arr[i], _ = strconv.Atoi(scanner.Text()) if i != 0 { arr[i] += arr[i - 1] } } /* 区间[l, r]的和可以使用区间[0, r]和[0, l - 1]相减得到, 在代码中即为arr[r]-arr[l-1]。这里需要注意l-1是否越界 */ for { var l, r int scanner.Scan() _, err := fmt.Sscanf(scanner.Text(), "%d %d", &l, &r) if err != nil { return } if l > 0 { fmt.Println(arr[r] - arr[l - 1]) } else { fmt.Println(arr[r]) } } } ```