素数筛法:埃氏筛法与线性筛法

素数筛法

埃氏筛法

Eratosthenes筛法 (埃拉托斯特尼筛法,简称埃氏筛法)
时间复杂度是 O ( n log ⁡ log ⁡ n ) O(n\log\log n) O(nloglogn)

如果我们从小到大考虑每个数,然后同时把当前这个数的所有(比自己大的)倍数记为合数,那么运行结束的时候没有被标记的数就是素数了。

例如:
考虑 2 时, 我们把 4, 6, 8, 10, 12, 14, 16, 18 … 均标记为合数。

考虑 3 时, 我们把 6, 9, 12, 15, 18, 21, 24, 27 … 均标记为合数。

考虑 4 时, 我们把 8, 12, 16, 20, 24, 28, 32, 36 … 均标记为合数。

考虑 5 时, 我们把 10, 15, 20, 25, 30, 35, 40, 45 … 均标记为合数。

显然这里对 4 的倍数的筛选是没有必要的,因为 4 是 2 的倍数,在筛选 2 的倍数时肯定会一并把 4 的倍数也筛掉。
同理我们也没有必要筛 6 的倍数,8 的倍数,9 的倍数,我们只需要根据素数的倍数筛选就行。
即我们只筛选 2 , 3 , 5 , 7 , 11 , 13 , 17 , ⋯ 2, 3, 5, 7, 11, 13, 17, \cdots 2,3,5,7,11,13,17, 的倍数即可。

而对于一个不超过 n n n 的合数来说,至少有两个质因子。
所以至少有一个质因子不超过 n \sqrt{n} n ,否则两个大于 n \sqrt{n} n 的质因子相乘肯定大于 n n n

如果要找到直到 n 为止的所有素数,仅对不超过 n \sqrt{n} n 的素数进行筛选就足够了。

vector<int> prime;
bool is_prime[N];

void Eratosthenes(int n) {
    // 初始化所有数的标记
    is_prime[0] = is_prime[1] = false;// 0和1不是素数
    for (int i = 2; i <= n; ++i) {
        is_prime[i] = true;
    }

    // i * i <= n 说明 i <= sqrt(n)
    for (int i = 2; i * i <= n; ++i) {
        if (is_prime[i])// 只对素数进行筛选
            for (int j = i + i; j <= n; j += i) {
                is_prime[j] = false;
            }
    }

    for (int i = 2; i <= n; ++i) {
        if (is_prime[i]) {
            prime.push_back(i);
        }
    }
}

线性筛法

线性筛法也称为 Euler 筛法 (欧拉筛法), 是埃氏算法的改进版本。

埃氏筛法仍有优化空间,它会将一个合数重复多次标记。
如果能让每个合数都只被标记一次,那么时间复杂度就可以降到 O ( n ) O(n) O(n) 了。

例如 :
6 会在筛 2 和 3 的倍数时重复出现。( 6 = 2 × 3 6 = 2 \times 3 6=2×3
12 会在筛 2 和 3 的倍数时重复出现。 ( 12 = 2 2 × 3 12 = 2^2 \times 3 12=22×3)
30 会在筛 2 、3 、5 的倍数时重复出现。( 30 = 2 × 3 × 5 30 = 2 \times 3 \times 5 30=2×3×5)

也就是说假设一个数 x = p 1 n 1 p 2 n 2 ⋯ p k n k x = {p_1}^{n_1}{p_2}^{n_2}\cdots{p_k}^{n_k} x=p1n1p2n2pknk
那么他就会在筛 p 1 , p 2 , ⋯   , p k p_1, p_2, \cdots, p_k p1,p2,,pk 的倍数时重复出现 k 次

我们希望每个合数仅被筛选一次,且 每个合数只被它的最小质因子筛去,不重复进行筛选。

而对于每一个合数 x x x, 设 x x x 的最小质因子为 p p p , 则 x x x 可以写成 x = k ∗ p x = k * p x=kp 的形式.

一个自然的想法是我们此时枚举最小质因子 p p p 的值,即筛去最小质因子为 2 , 3 , 5 , ⋯ 2, 3, 5, \cdots 2,3,5, 的数。

但是此时不能像普通的埃氏算法那样直接筛去 p p p 的倍数,因为 p p p 的倍数的最小质因子不一定是 p p p

例如 对于 3 而言, 3 的倍数中 6,12,18 等数的最小质因子都是 2, 只有 9, 15, 21 等的最小质因子为 3 。

实际上这里筛去某个素数 p p p k k k 倍时,必须保证 k k k 的任意一个质因子均不小于 p p p ,否则 k ∗ p k * p kp 的最小质因子必定不是 p p p

p = 5 p = 5 p=5 为例, p p p 的倍数有 10 , 15 , 20 , 25 , 30 , 35 , ⋯ 10, 15, 20, 25, 30 ,35, \cdots 10,15,20,25,30,35, k k k 的质因子中不能包含 2 和 3, 只能包含 5 , 7 , 11 , 13 , ⋯ 5, 7, 11, 13, \cdots 5,7,11,13,
所以我们应该筛去的是 25 , 35 , 55 , 65 , ⋯ 25, 35, 55, 65, \cdots 25,35,55,65,, 至于 10 我们在筛 2 的倍数时候筛去, 15 在筛 3 的倍数时筛去。

但是我们没法直接知道大于 p p p 的素数(对于上面来说是 5 , 7 , 11 , 13 5,7,11,13 5,7,11,13等),这正是我们需要求的问题。

因此我们重新考虑枚举最小质因子的倍数 k 的值。

我们先来看一些具体的例子
4 = 2 ∗ 2 ( k = 2 , p = 2 ) 6 = 3 ∗ 2 ( k = 3 , p = 2 ) 8 = 4 ∗ 2 ( k = 4 , p = 2 ) 9 = 3 ∗ 3 ( k = 3 , p = 3 ) 10 = 5 ∗ 2 ( k = 5 , p = 2 ) 12 = 6 ∗ 2 ( k = 6 , p = 2 ) 15 = 5 ∗ 3 ( k = 5 , p = 3 ) 20 = 10 ∗ 2 ( k = 10 , p = 2 ) 30 = 15 ∗ 2 ( k = 15 , p = 2 ) 35 = 7 ∗ 5 ( k = 7 , p = 5 ) 46 = 23 ∗ 2 ( k = 23 , p = 2 ) 4 = 2 * 2\quad(k = 2, p = 2)\\ 6 = 3 * 2\quad(k = 3, p = 2)\\ 8 = 4 * 2\quad(k = 4, p = 2)\\ 9 = 3 * 3\quad(k = 3, p = 3)\\ 10 = 5 * 2\quad(k = 5, p = 2)\\ 12 = 6 * 2\quad(k = 6, p = 2)\\ 15 = 5 * 3\quad(k = 5, p = 3)\\ 20 = 10 * 2\quad(k = 10, p = 2)\\ 30 = 15 * 2\quad(k = 15, p = 2)\\ 35 = 7 * 5\quad(k = 7, p = 5)\\ 46 = 23 * 2\quad(k = 23, p = 2)\\ 4=22(k=2,p=2)6=32(k=3,p=2)8=42(k=4,p=2)9=33(k=3,p=3)10=52(k=5,p=2)12=62(k=6,p=2)15=53(k=5,p=3)20=102(k=10,p=2)30=152(k=15,p=2)35=75(k=7,p=5)46=232(k=23,p=2)

初步来看 k k k 需要枚举 2 到 n 内的所有数,而由前面知 k k k 的任意一个质因子均不小于 p p p , 即至少 k ≥ p k\ge p kp
且对于一个固定的 k k k 来说 p p p 不能大于 k k k 的最小质因子 ,否则 k ∗ p k * p kp 的最小质因子必定不是 p p p

对于每一个 k k k p p p 从 2 不断枚举, 直到 k ∗ p > n k*p>n kp>n 或者 k % p = = 0 k\% p == 0 k%p==0
前者说明要筛的数超过了 n n n 的范围,我们不再关心。
后者说明此时的 p p p 恰好是 k k k 的最小质因子,因为 p p p 是从2开始不断增大的,第一次能整除 k k k 的就是 k k k 的最小质因子。
此时我们不用再枚举 p + 1 p+1 p+1了,因为 p + 1 p+1 p+1 肯定大于 k k k 的最小质因子 p p p

vector<int> prime;
bool is_prime[N];

void Euler(int n) {
    // 初始化所有数的标记
    is_prime[0] = is_prime[1] = false; // 0和1不是素数
    for (int i = 2; i <= n; ++i) {
        is_prime[i] = true;
    }

    for (int i = 2; i <= n; ++i) {
        if (is_prime[i]) {
            prime.push_back(i);
        }
        for (int j = 0 ; j < prime.size(); ++j) {
            int x = i * prime[j]; // 需要筛去的数
            if (x > n) break;
            is_prime[x] = false;
            if (i % prime[j] == 0) break;
        }
    }

    for (int i = 2; i <= n; ++i) {
        if (is_prime[i]) {
            prime.push_back(i);
        }
    }
}

参考自 https://oi-wiki.org/math/number-theory/sieve/#素数筛法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值