题解:学而思编程 区间质数
【题目来源】
学而思编程:区间质数
【题目描述】
质数是指在大于 \(1\) 的自然数中,除了 \(1\) 和它本身以外不再有其他因数的自然数,例如:\(2,3,5,7,11,...\)。
小猴最近学习了质数,猴博士为了考察小猴对质数的理解,决定给小猴出一道题目:
给定一个长度为 \(n\) 的数列 \(a_1,a_2,\dots,a_n\),猴博士会向小猴提出 \(q\) 次询问,每次询问,小猴需要回答在该数列中区间 \([l,r]\) 中有多少个质数,区间 \([l,r]\) 表示数列中的第 \(l\) 个到第 \(r\) 个之间(包含 \(l\) 和 \(r\))所有的数字。
小猴不懂信息学,只会通过数学方法一个一个手算。小猴决定找自己学习信息学的好朋友来帮助他快速解决这个问题,请你帮助小猴完成这个问题。
【输入】
第一行,包含一个整数 \(n\),表示数列长度;
第二行,包含 \(n\) 个正整数 \(a_1,a_2,\dots,a_n\),表示给定的数列;
第三行,包含一个整数 \(q\),表示询问次数;
接下来 \(q\) 行,每行两个整数 \(l,r\),表示每次询问的区间。
【输出】
共 \(q\) 行,每行一个整数,第 \(i\) 个整数表示第 \(i\) 次询问的结果。
【输入样例】
8
1 2 3 4 5 6 7 8
3
1 3
2 6
1 8
【输出样例】
2
3
4
【核心思想】
-
问题分析:给定长度为 \(n\) 的数列 \(a_1, a_2, \ldots, a_n\),\(q\) 次询问区间 \([l, r]\) 内的质数个数。这是一个埃氏筛 + 前缀和问题,关键在于离线预处理质数表,将每次区间查询优化到 \(O(1)\)。
-
算法选择:
- 埃拉托斯特尼筛法(埃氏筛):预处理 \([2, 10^7]\) 范围内所有质数,\(O(V \log \log V)\)
- 前缀和:
s[i]表示前 \(i\) 个元素中质数的个数,区间查询 \(O(1)\)
-
关键步骤:
- 质数筛预处理:
flag[x] = true表示 \(x\) 是质数,初始化全部为trueflag[0] = flag[1] = false:\(0\) 和 \(1\) 不是质数- 遍历 \(i\) 从 \(2\) 到 \(10^7\):
- 若
flag[i] = true(\(i\) 是质数):- 遍历 \(j\) 从 \(2\) 开始,标记所有 \(i \times j\) 为合数:
flag[i \times j] = false
- 遍历 \(j\) 从 \(2\) 开始,标记所有 \(i \times j\) 为合数:
- 若
- 构建前缀和:
- 读取 \(n\) 和数列 \(a[1..n]\)
s[i] = s[i-1] + flag[a_i]:前 \(i\) 个元素中质数的累计个数
- 处理 \(q\) 次询问:
- 读取区间 \([l, r]\)
ans = s[r] - s[l-1]:区间 \([l, r]\) 内的质数个数- 输出
ans
- 质数筛预处理:
-
时间/空间复杂度:
- 时间复杂度:\(O(V \log \log V + n + q)\),埃氏筛 \(O(V \log \log V)\),前缀和构建 \(O(n)\),每次查询 \(O(1)\)
- 空间复杂度:\(O(V + n)\),质数标记数组
flag[0..V],前缀和数组s[0..n]
-
离线预处理的核心思想:
- 查询与数据分离:数列元素值域固定(\(a_i \leq 10^7\)),与询问无关,可预先筛出所有质数
- 布尔标记数组:
flag[x]实现 \(O(1)\) 质数判定,将"是否为质数"的数学问题转化为数组查表 - 前缀和降维:区间 \([l, r]\) 内质数个数 = 前 \(r\) 个的质数个数 \(-\) 前 \(l-1\) 个的质数个数,将 \(O(r-l+1)\) 的遍历优化为 \(O(1)\)
- 埃氏筛原理:质数的倍数一定是合数,从小到大遍历,每个合数被其最小质因子筛除,时间复杂度接近线性
- 适用于"多区间查询 + 固定值域判定"类问题,离线预处理将重复判定优化为一次筛法 + 多次 \(O(1)\) 查询
【解题思路】

【算法标签】
质数
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6+10;
bool flag[10*N];
int a[N], s[N];
int main()
{
memset(flag, true, sizeof(flag)); // 质数筛
flag[0] = flag[1] = false;
for (int i=2; i<=1e7; i++) {
if (flag[i]) {
for (int j=2; i*j<=1e7; j++) {
flag[i*j] = false;
}
}
}
int n;
scanf("%d", &n); // 输入n
for (int i=1; i<=n; i++) { // 遍历n个数
scanf("%d", &a[i]); // 输入n个数,要用scanf进行优化
s[i] = s[i-1] + flag[a[i]]; // 使用前缀和,记录i及以前的质数个数
}
int q;
scanf("%d", &q);
while (q--) { // 遍历q次询问
int l, r;
scanf("%d%d", &l, &r); // 输入l和r
printf("%d\n", s[r]-s[l-1]); // 直接得到[l,r]区间的质数
}
return 0;
}
【运行结果】1
8
1 2 3 4 5 6 7 8
3
1 3
2
2 6
3
1 8
4
浙公网安备 33010602011771号