题解:AcWing 868 筛质数
【题目来源】
AcWing:868 筛质数 - AcWing题库
【题目描述】
给定一个正整数 \(n\),请你求出 \(1\sim n\) 中质数的个数。
【输入】
共一行,包含整数 \(n\)。
【输出】
共一行,包含一个整数,表示 \(1\sim n\) 中质数的个数。
【输入样例】
8
【输出样例】
4
【核心思想】
-
问题分析:给定正整数 \(n\),需要统计区间 \([1, n]\) 内所有质数的个数。由于数据范围通常较大(可达 \(10^6\) 甚至更大),若对每个数单独使用试除法判定,总复杂度高达 \(O(n\sqrt{n})\),无法接受。因此需要利用筛法在预处理阶段一次性求出 \([1, n]\) 内的所有质数。
-
算法选择:
- 埃拉托斯特尼筛法(埃氏筛):对于每个找到的质数 \(p\),将其所有倍数 \(2p, 3p, \dots\) 标记为合数。时间复杂度 \(O(n \log \log n)\),实现简单直观
- 线性筛(欧拉筛):保证每个合数仅被其最小质因数筛去一次。当遍历到合数 \(i\) 的最小质因数 \(p_j\) 时(即 \(i \bmod p_j = 0\)),立即停止内层循环。时间复杂度严格 \(O(n)\),常数更小
-
关键步骤:
- 初始化:创建布尔数组
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]\) 内质数的个数
- 初始化:创建布尔数组
-
时间/空间复杂度:
- 埃氏筛:时间复杂度 \(O(n \log \log n)\),空间复杂度 \(O(n)\)
- 线性筛:时间复杂度 \(O(n)\),空间复杂度 \(O(n)\)
-
筛法的核心思想:
- 质数的倍数必为合数:若 \(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
浙公网安备 33010602011771号