质数与筛法
💡 核心思想
质数是大于 1 且只能被 1 和自身整除的整数。筛法用于快速找出 $[1, n]$ 内的所有质数。埃氏筛 $O(n \log \log n)$,欧拉筛(线性筛)$O(n)$。质因数分解也是常见操作。
欧拉筛的核心思想是"每个合数只被它的最小质因子筛一次"。维护一个质数表,对于每个数 $i$,用表中所有质数 $p$ 去筛 $i \times p$,当 $p | i$ 时就停止(因为更大的质因子会由 $i/p \times p$ 来筛)。这样就保证了每个合数只被筛一次。
🎯 直觉理解
筛法就像"排除法"。你要找 1 到 100 里的质数。先把 2 的倍数划掉(4,6,8...),再把 3 的倍数划掉(6,9,12...),以此类推。最后剩下的就是质数。欧拉筛更聪明——它确保每个合数只被划掉一次,不多不少。
📝 算法流程
- 埃氏筛:标记每个质数的倍数
- 欧拉筛:每个合数只被最小质因子筛一次
- 质因数分解:试除法 $O(\sqrt{n})$
- 判定质数:试除法或 Miller-Rabin
$$\text{埃氏筛复杂度:} O(n \log \log n) \approx O(n)$$
$$\text{欧拉筛复杂度:} O(n)$$
📊 复杂度分析
| 指标 | 复杂度 |
|---|---|
| 时间 | 筛法 $O(n)$,判质数 $O(\sqrt{n})$ |
| 空间 | $O(n)$ |
💻 参考实现(C++)
C++ (C++17)
#include <bits/stdc++.h>
using namespace std;
const int N = 10000005;
bool notPrime[N]; int primes[N], cnt = 0;
void eulerSieve(int n) {
for (int i = 2; i <= n; i++) {
if (!notPrime[i]) primes[cnt++] = i;
for (int j = 0; j < cnt && (long long)i * primes[j] <= n; j++) {
notPrime[i * primes[j]] = true;
if (i % primes[j] == 0) break; // 关键:保证每个合数只被筛一次
}
}
}
int main() {
int n; cin >> n;
eulerSieve(n);
cout << cnt << endl;
return 0;
}⚠️ 常见坑点
欧拉筛中 i % primes[j] == 0 时必须 break
筛法数组从 2 开始,1 不是质数
大范围筛法注意空间
i * primes[j] 可能溢出 int
📚 相关题目
| 题目 | 来源 | 难度 | 备注 |
|---|---|---|---|
| P3383 线性筛素数 | 洛谷 | CSP-S | 欧拉筛模板 |
| P3912 素数个数 | 洛谷 | CSP-J | 筛法计数 |