P1865 A % B Problem
题目理解
这道题要求我们处理多个查询,每个查询给出一个区间 [l, r],我们需要统计这个区间内质数的数量。如果查询区间超出了预先设定的范围 [1, m],则需要输出错误提示。
解题方法
-
预处理质数表:使用埃拉托斯特尼筛法(筛法)预处理出1到m之间的所有质数,并计算前缀和数组s[],其中s[i]表示从1到i的质数个数。
-
处理查询:对于每个查询,首先检查区间是否合法(在1到m范围内),如果合法则利用前缀和数组快速计算区间质数个数,否则输出错误信息。
参考程序
#include<bits/stdc++.h>
#define N 1000005 // 定义最大范围
using namespace std;
bool vis[N]; // 标记数组,vis[i]=true表示i不是质数
int pri[N], // 存储所有质数
s[N], // 前缀和数组,s[i]表示1到i的质数个数
cnt; // 质数计数器
// 筛法求质数
void prime(int n) {
for (int i = 2; i <= n; i++) {
if (vis[i] == 0) { // i是质数
pri[++cnt] = i; // 将i存入质数数组
s[i] = s[i - 1] + 1; // 更新前缀和
} else {
s[i] = s[i - 1]; // 不是质数,前缀和不变
}
// 筛去i的倍数
for (int j = 1; j <= cnt; j++) {
if (i * pri[j] > n) break; // 超过范围则停止
vis[pri[j]*i] = 1; // 标记i*pri[j]为非质数
if (i % pri[j] == 0) break; // 关键优化:保证每个数只被最小质因数筛去
}
}
}
int main() {
int n, m;
cin >> n >> m; // 读取查询次数n和最大值m
prime(m); // 预处理1到m的质数
while (n--) {
int L, R;
cin >> L >> R; // 读取查询区间
// 检查区间是否合法
if (R > m || L < 1) {
cout << "Crossing the line" << endl;
} else {
// 使用前缀和数组快速计算区间质数个数
cout << s[R] - s[L - 1] << endl;
}
}
return 0;
}
算法分析
-
筛法时间复杂度:预处理阶段的时间复杂度是O(n log log n),这是筛法的标准时间复杂度。
-
查询时间复杂度:每次查询只需要O(1)时间,因为使用了前缀和数组。
-
空间复杂度:使用了三个大小为N的数组,空间复杂度是O(n)。
优化点
-
筛法优化:代码中使用了
if (i % pri[j] == 0) break这一优化,确保每个合数只被它的最小质因数筛去,这是线性筛法的关键。 -
前缀和优化:预处理前缀和数组使得区间查询可以在常数时间内完成。
注意事项
-
边界处理:需要特别注意查询区间是否在有效范围内。
-
数组大小:根据题目中m的最大值(1e6)来定义数组大小,避免越界。
这个解法高效地解决了问题,预处理阶段完成后,每个查询都能在常数时间内得到结果,非常适合处理大量查询的情况。

浙公网安备 33010602011771号