题解:学而思编程 区间质数

【题目来源】

学而思编程:区间质数

【题目描述】

质数是指在大于 \(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

【核心思想】

  1. 问题分析:给定长度为 \(n\) 的数列 \(a_1, a_2, \ldots, a_n\)\(q\) 次询问区间 \([l, r]\) 内的质数个数。这是一个埃氏筛 + 前缀和问题,关键在于离线预处理质数表,将每次区间查询优化到 \(O(1)\)

  2. 算法选择

    • 埃拉托斯特尼筛法(埃氏筛):预处理 \([2, 10^7]\) 范围内所有质数,\(O(V \log \log V)\)
    • 前缀和s[i] 表示前 \(i\) 个元素中质数的个数,区间查询 \(O(1)\)
  3. 关键步骤

    • 质数筛预处理
      • flag[x] = true 表示 \(x\) 是质数,初始化全部为 true
      • flag[0] = flag[1] = false\(0\)\(1\) 不是质数
      • 遍历 \(i\)\(2\)\(10^7\)
        • flag[i] = true\(i\) 是质数):
          • 遍历 \(j\)\(2\) 开始,标记所有 \(i \times j\) 为合数:flag[i \times j] = false
    • 构建前缀和
      • 读取 \(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
  4. 时间/空间复杂度

    • 时间复杂度:\(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]
  5. 离线预处理的核心思想

    • 查询与数据分离:数列元素值域固定(\(a_i \leq 10^7\)),与询问无关,可预先筛出所有质数
    • 布尔标记数组flag[x] 实现 \(O(1)\) 质数判定,将"是否为质数"的数学问题转化为数组查表
    • 前缀和降维:区间 \([l, r]\) 内质数个数 = 前 \(r\) 个的质数个数 \(-\)\(l-1\) 个的质数个数,将 \(O(r-l+1)\) 的遍历优化为 \(O(1)\)
    • 埃氏筛原理:质数的倍数一定是合数,从小到大遍历,每个合数被其最小质因子筛除,时间复杂度接近线性
    • 适用于"多区间查询 + 固定值域判定"类问题,离线预处理将重复判定优化为一次筛法 + 多次 \(O(1)\) 查询

【解题思路】

img/Pasted image 20260519125519.png

【算法标签】

质数

【代码详解】

#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
posted @ 2026-07-04 14:19  团爸讲算法  阅读(6)  评论(0)    收藏  举报