题解:AcWing 868 筛质数

【题目来源】

AcWing:868 筛质数 - AcWing题库

【题目描述】

给定一个正整数 \(n\),请你求出 \(1\sim n\) 中质数的个数。

【输入】

共一行,包含整数 \(n\)

【输出】

共一行,包含一个整数,表示 \(1\sim n\) 中质数的个数。

【输入样例】

8

【输出样例】

4

【核心思想】

  1. 问题分析:给定正整数 \(n\),需要统计区间 \([1, n]\) 内所有质数的个数。由于数据范围通常较大(可达 \(10^6\) 甚至更大),若对每个数单独使用试除法判定,总复杂度高达 \(O(n\sqrt{n})\),无法接受。因此需要利用筛法在预处理阶段一次性求出 \([1, n]\) 内的所有质数。

  2. 算法选择

    • 埃拉托斯特尼筛法(埃氏筛):对于每个找到的质数 \(p\),将其所有倍数 \(2p, 3p, \dots\) 标记为合数。时间复杂度 \(O(n \log \log n)\),实现简单直观
    • 线性筛(欧拉筛):保证每个合数仅被其最小质因数筛去一次。当遍历到合数 \(i\) 的最小质因数 \(p_j\) 时(即 \(i \bmod p_j = 0\)),立即停止内层循环。时间复杂度严格 \(O(n)\),常数更小
  3. 关键步骤

    • 初始化:创建布尔数组 st[1..n] 标记合数,质数数组 primes[] 及计数器 cnt
    • 埃氏筛流程
      • 遍历 \(i\)\(2\)\(n\)
      • st[i] == false,则 \(i\) 为质数,加入 primes[cnt++]
      • \(j = 2i\) 开始,步长为 \(i\),标记 st[j] = true(所有 \(i\) 的倍数均为合数)
    • 线性筛流程
      • 遍历 \(i\)\(2\)\(n\)
      • st[i] == false,则 \(i\) 为质数,加入 primes[cnt++]
      • 遍历已找到的质数 primes[j],条件为 primes[j] \leq n / i
        • 标记 st[primes[j] * i] = true
        • 关键判断:若 i \% primes[j] == 0,则 primes[j]\(i\) 的最小质因数,立即 break,确保每个合数只被筛一次
    • 输出结果cnt 即为 \([1, n]\) 内质数的个数
  4. 时间/空间复杂度

    • 埃氏筛:时间复杂度 \(O(n \log \log n)\),空间复杂度 \(O(n)\)
    • 线性筛:时间复杂度 \(O(n)\),空间复杂度 \(O(n)\)
  5. 筛法的核心思想

    • 质数的倍数必为合数:若 \(p\) 是质数,则 \(kp\)\(k \geq 2\))一定是合数,据此可以批量标记
    • 埃氏筛的冗余:一个合数可能被多个质数重复标记(如 \(6\) 会被 \(2\)\(3\) 各标记一次),导致常数较大
    • 线性筛的唯一性保证:通过 i \% primes[j] == 0 时的 break,确保合数 \(x\) 只在被其最小质因数 \(p_{\min}\)\(x/p_{\min}\) 组合时标记一次,从而将复杂度降至线性
    • 适用于大规模区间质数筛选、积性函数预处理等场景

【解题思路】

【算法标签】

质数

【代码详解】

// 埃式筛法
#include <bits/stdc++.h>
using namespace std;

const int N = 1000005;  // 定义筛法上限

int primes[N], cnt;     // primes数组存储素数,cnt记录素数个数
bool st[N];             // 标记数组,false表示是素数,true表示不是素数

// 埃拉托斯特尼筛法求素数
void get_primes(int n)
{
    // 从2开始遍历到n
    for (int i = 2; i <= n; i++)
    {
        // 如果当前数未被标记为非素数
        if (!st[i])
        {
            primes[cnt++] = i;  // 将当前数加入素数数组

            // 标记当前素数的所有倍数为非素数
            for (int j = i + i; j <= n; j += i)
                st[j] = true;
        }
    }
}

int main()
{
    int n;  // 输入的上限值
    cin >> n;

    // 调用筛法函数获取素数
    get_primes(n);

    // 输出素数个数
    cout << cnt << endl;

    return 0;
}
// 线性筛
#include <bits/stdc++.h>
using namespace std;

const int N = 1000005;  // 定义筛法上限

int primes[N], cnt;     // primes数组存储素数,cnt记录素数个数
bool st[N];             // 标记数组,false表示是素数,true表示合数

// 线性筛法(欧拉筛)求素数
void get_primes(int n)
{
    // 从2开始遍历到n
    for (int i = 2; i <= n; i++)
    {
        // 如果i未被标记为合数,则i是素数
        if (!st[i])
            primes[cnt++] = i;  // 将i加入素数数组

        // 用当前已找到的素数筛选合数
        for (int j = 0; primes[j] <= n / i; j++)
        {
            st[primes[j] * i] = true;  // 标记素数的倍数为合数

            // 关键优化:当primes[j]是i的最小质因子时终止筛选
            if (i % primes[j] == 0)
                break;
        }
    }
}

int main()
{
    int n;  // 输入的上限值
    cin >> n;

    // 调用线性筛法函数获取素数
    get_primes(n);

    // 输出素数个数
    cout << cnt << endl;

    return 0;
}

【运行结果】

8
4
posted @ 2026-06-21 11:24  团爸讲算法  阅读(10)  评论(0)    收藏  举报