题目链接

质数统计

题目描述

给定 次询问,每次询问一个闭区间 ,请你输出该区间内质数(素数)的数量。

解题思路

本题要求对多个区间进行质数数量的统计。如果对每个查询都遍历区间 并逐个判断数字是否为质数,当区间很大或查询次数很多时,效率会非常低下,导致超时。

这是一个典型的可以使用预处理前缀和思想解决的区间查询问题。

  1. 预处理质数

    首先,我们需要一个高效的方法来判断在一个较大范围内哪些数是质数。埃拉托斯特尼筛法(埃氏筛)是完成这项任务的理想选择。我们可以创建一个布尔数组 ,预先筛出从 到数据上限(例如 )的所有质数。

  2. 构建前缀和数组

    为了能够快速查询任意区间 内的质数数量,我们可以构建一个前缀和数组。我们定义一个数组 ,其中 存储区间 内质数的总数量。

    这个数组可以基于埃氏筛的结果 数组来计算:

    • 对于 ,递推公式为:
  3. 响应查询

    在完成了预处理之后,对于任何一个查询区间 ,其内部的质数数量可以通过前缀和数组在 的时间内计算出来。

    其计算公式为:

    这个公式的含义是:用 区间内的质数总数减去 区间内的质数总数,剩下的就是区间 内的质数总数。

综上,我们首先进行一次性的筛法和前缀和计算,然后就可以快速回答所有查询了。

代码

#include <iostream>
#include <vector>

using namespace std;

const int MAX_R = 1000001;
vector<bool> is_prime(MAX_R, true);
vector<int> prefix_sum(MAX_R, 0);

void sieve_and_prefix_sum() {
    is_prime[0] = is_prime[1] = false;
    for (int p = 2; p * p < MAX_R; ++p) {
        if (is_prime[p]) {
            for (int i = p * p; i < MAX_R; i += p) {
                is_prime[i] = false;
            }
        }
    }

    for (int i = 1; i < MAX_R; ++i) {
        prefix_sum[i] = prefix_sum[i - 1];
        if (is_prime[i]) {
            prefix_sum[i]++;
        }
    }
}

int main() {
    sieve_and_prefix_sum();
    
    int Q;
    cin >> Q;
    while (Q--) {
        int l, r;
        cin >> l >> r;
        cout << prefix_sum[r] - prefix_sum[l - 1] << endl;
    }
    
    return 0;
}
import java.util.Scanner;
import java.util.Arrays;

public class Main {
    static final int MAX_R = 1000001;
    static boolean[] is_prime = new boolean[MAX_R];
    static int[] prefix_sum = new int[MAX_R];

    static {
        Arrays.fill(is_prime, true);
        is_prime[0] = is_prime[1] = false;
        for (int p = 2; p * p < MAX_R; p++) {
            if (is_prime[p]) {
                for (int i = p * p; i < MAX_R; i += p) {
                    is_prime[i] = false;
                }
            }
        }

        prefix_sum[0] = 0;
        for (int i = 1; i < MAX_R; i++) {
            prefix_sum[i] = prefix_sum[i - 1];
            if (is_prime[i]) {
                prefix_sum[i]++;
            }
        }
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int Q = sc.nextInt();
        while (Q-- > 0) {
            int l = sc.nextInt();
            int r = sc.nextInt();
            System.out.println(prefix_sum[r] - prefix_sum[l - 1]);
        }
    }
}
MAX_R = 1000001
is_prime = [True] * MAX_R
prefix_sum = [0] * MAX_R

def sieve_and_prefix_sum():
    global is_prime, prefix_sum
    is_prime[0] = is_prime[1] = False
    for p in range(2, int(MAX_R**0.5) + 1):
        if is_prime[p]:
            for i in range(p * p, MAX_R, p):
                is_prime[i] = False
    
    for i in range(1, MAX_R):
        prefix_sum[i] = prefix_sum[i - 1]
        if is_prime[i]:
            prefix_sum[i] += 1

def main():
    sieve_and_prefix_sum()
    Q = int(input())
    for _ in range(Q):
        l, r = map(int, input().split())
        print(prefix_sum[r] - prefix_sum[l - 1])

if __name__ == "__main__":
    main()

算法及复杂度

  • 算法:数论、埃拉托斯特尼筛法、前缀和

  • 时间复杂度:预处理阶段,埃氏筛法的时间复杂度为 ,计算前缀和的复杂度为 ,其中 是区间的最大右端点。处理 次查询的总时间复杂度为

  • 空间复杂度:需要一个布尔数组和一个整数数组来存储质数信息和前缀和,空间复杂度为