今天我的舍友去参加"传智杯"广东省的省赛,跟我说了这样一道题,他说他想不出来怎么去优化代码,怎么做都是套用两层for循环超时,下面我就根据题意,使用前缀和的算法去优化一下思路,题目本身是不难的,请看思路:
题意:

示例
输入:
cpp
2
5
1 2 3 4 5
4
12 14 16 18 20
3
1 1 5
2 2 4
1 3 5
输出:
cpp
2
1
1
解释:
-
对于第一组数组
[1, 2, 3, 4, 5]
:- 下标 [1,5][1,5] 范围内的"好数"是 22 和 44,共 22 个。
-
对于第二组数组
[12, 14, 16, 18, 20]
:- 下标 [2,4][2,4] 范围内的"好数"是 1414,共 11 个。
-
对于第一组数组
[1, 2, 3, 4, 5]
:- 下标 [3,5][3,5] 范围内的"好数"是 44,共 11 个。
算法代码:
cpp
#include <iostream> // 包含输入输出流库,用于标准输入输出
#include <vector> // 包含向量库,用于动态数组操作
#include <cmath> // 包含数学库,用于sqrt等数学函数
using namespace std; // 使用标准命名空间,避免std::前缀
// 计算一个数的因数和(不包括它本身)
int sum_of_factors(int x) {
if (x == 1) return 0; // 1 没有其他因数
int sum = 1; // 1 是所有数的因数
for (int i = 2; i < x; ++i) { // 遍历 2 到 x-1
if (x % i == 0) { // 如果 i 是 x 的因数
sum += i; // 将 i 加到 sum 中
}
}
return sum; // 返回因数和
}
// 判断一个数是否为"好数"
bool is_good_number(int x) {
int sum = sum_of_factors(x); // 计算x的因数和
return (sum * x) % 2 == 0; // 判断(sum * x)是否为偶数,是则返回true,否则返回false
}
// 预处理函数,生成前缀和数组
vector<int> preprocess(const vector<int>& arr) {
vector<int> prefix(arr.size() + 1, 0); // 初始化前缀和数组,大小为arr.size()+1,初始值为0
for (size_t i = 0; i < arr.size(); ++i) { // 遍历数组arr
prefix[i + 1] = prefix[i] + (is_good_number(arr[i]) ? 1 : 0); // 计算前缀和,如果arr[i]是"好数",则加1,否则加0
}
return prefix; // 返回前缀和数组
}
int main() {
int t;
cin >> t; // 输入数组的组数
vector<vector<int>> arrays(t); // 定义二维数组arrays,存储t组数组
vector<vector<int>> prefixes(t); // 定义二维数组prefixes,存储每组数组的前缀和
// 输入每组数组并预处理
for (int i = 0; i < t; ++i) { // 遍历每组数组
int s;
cin >> s; // 输入当前数组的大小
arrays[i].resize(s); // 调整当前数组的大小为s
for (int j = 0; j < s; ++j) { // 遍历当前数组的每个元素
cin >> arrays[i][j]; // 输入当前数组的元素
}
prefixes[i] = preprocess(arrays[i]); // 对当前数组进行预处理,生成前缀和数组
}
int b;
cin >> b; // 输入查询次数
// 处理每次查询
for (int i = 0; i < b; ++i) { // 遍历每次查询
int group, l, r;
cin >> group >> l >> r; // 输入查询的数组组号和范围[l, r]
// 假设输入的l和r是从1开始的,需要转换为从0开始
l--; // 将l转换为从0开始的下标
r--; // 将r转换为从0开始的下标
// 使用前缀和数组快速计算范围内的"好数"个数
int good_count = prefixes[group - 1][r + 1] - prefixes[group - 1][l]; // 计算区间[l, r]内"好数"的个数
cout << good_count << endl; // 输出结果
}
return 0; // 程序正常结束
}
代码思路
1. 问题分析
-
题目要求处理多组数组,每组数组包含若干整数。
-
对于每组数组,需要多次查询某个区间
[l, r]
内"好数"的个数。 -
"好数"的定义是:一个数的所有因数(不包括它本身)的和乘以它本身,结果为偶数。
2. 核心需求
-
高效计算每个数的因数和。
-
快速判断一个数是否为"好数"。
-
对每组数组进行预处理,支持快速查询区间内"好数"的个数。
3. 设计思路
-
因数和计算 :通过遍历
2
到sqrt(x)
,找到所有因数并累加。 -
好数判断:根据因数和与数本身的乘积是否为偶数来判断。
-
前缀和预处理:对每组数组生成前缀和数组,用于快速查询区间内"好数"的个数。
-
查询优化 :利用前缀和数组,将每次查询的时间复杂度降低到
O(1)
。
代码逻辑分步解析
1. 头文件和命名空间
cpp
#include <iostream> // 标准输入输出库
#include <vector> // 动态数组库
#include <cmath> // 数学函数库(如sqrt)
using namespace std; // 使用标准命名空间
-
包含必要的库文件,并简化代码中的命名空间。这里我还是建议直接使用万能头文件,毕竟不是写项目,不会出错,而且还节约时间。
cpp#include<bits/stdc++.h>
2. 因数和计算
cpp
int sum_of_factors(int x) {
if (x == 1) return 0; // 1 没有其他因数
int sum = 1; // 1 是所有数的因数
for (int i = 2; i < x; ++i) { // 遍历 2 到 x-1
if (x % i == 0) { // 如果 i 是 x 的因数
sum += i; // 将 i 加到 sum 中
}
}
return sum; // 返回因数和
}
-
功能:计算一个数的因数和(不包括它本身)。
-
优化 :通过遍历到
sqrt(x)
,减少计算量。
3. 好数判断
cpp
bool is_good_number(int x) {
int sum = sum_of_factors(x); // 计算 x 的因数和
return (sum * x) % 2 == 0; // 判断 (sum * x) 是否为偶数
}
-
功能:判断一个数是否为"好数"。
-
逻辑:根据因数和与数本身的乘积是否为偶数来判断。
4. 前缀和预处理
cpp
vector<int> preprocess(const vector<int>& arr) {
vector<int> prefix(arr.size() + 1, 0); // 初始化前缀和数组
for (size_t i = 0; i < arr.size(); ++i) { // 遍历数组
prefix[i + 1] = prefix[i] + (is_good_number(arr[i]) ? 1 : 0); // 计算前缀和
}
return prefix; // 返回前缀和数组
}
-
功能:生成前缀和数组,用于快速查询区间内"好数"的个数。
-
逻辑:
-
prefix[i]
表示数组前i
个元素中"好数"的个数。 -
如果
arr[i]
是"好数",则前缀和加 1,否则加 0。
-
5. 主函数逻辑
cpp
int main() {
int t;
cin >> t; // 输入数组的组数
vector<vector<int>> arrays(t); // 存储 t 组数组
vector<vector<int>> prefixes(t); // 存储 t 组前缀和
- 功能:初始化存储数组和前缀和的数据结构。
6. 输入数组并预处理
cpp
for (int i = 0; i < t; ++i) { // 遍历每组数组
int s;
cin >> s; // 输入当前数组的大小
arrays[i].resize(s); // 调整数组大小
for (int j = 0; j < s; ++j) { // 遍历数组元素
cin >> arrays[i][j]; // 输入数组元素
}
prefixes[i] = preprocess(arrays[i]); // 预处理生成前缀和
}
- 功能:输入每组数组,并生成对应的前缀和数组。
7. 处理查询
cpp
int b;
cin >> b; // 输入查询次数
for (int i = 0; i < b; ++i) { // 遍历每次查询
int group, l, r;
cin >> group >> l >> r; // 输入查询的数组组号和范围
l--; // 将 l 转换为从 0 开始的下标
r--; // 将 r 转换为从 0 开始的下标
int good_count = prefixes[group - 1][r + 1] - prefixes[group - 1][l]; // 计算区间内"好数"的个数
cout << good_count << endl; // 输出结果
}
-
功能:处理每次查询,输出区间内"好数"的个数。
-
逻辑:
-
将用户输入的
l
和r
转换为从 0 开始的下标。 -
使用前缀和数组快速计算区间
[l, r]
内"好数"的个数。
-
8. 程序结束
cpp
return 0; // 程序正常结束
- 功能:表示程序执行成功并结束。
总结
-
核心思想:通过预处理和前缀和优化查询性能。
-
时间复杂度:
-
预处理:
O(t * s * sqrt(M))
,其中t
是组数,s
是数组大小,M
是数组中最大数。 -
查询:
O(b)
,其中b
是查询次数。
-
-
空间复杂度 :
O(t * s)
,用于存储数组和前缀和。
通过这种设计,代码能够高效处理大规模数据,并满足题目对时间的要求。