质数与筛法CSP-J

目录

💡 核心思想

质数是大于 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...),以此类推。最后剩下的就是质数。欧拉筛更聪明——它确保每个合数只被划掉一次,不多不少。

📝 算法流程

  1. 埃氏筛:标记每个质数的倍数
  2. 欧拉筛:每个合数只被最小质因子筛一次
  3. 质因数分解:试除法 $O(\sqrt{n})$
  4. 判定质数:试除法或 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筛法计数