【算法学习笔记】30:埃氏筛(Sieve of Eratosthenes)和线性筛(Linear Sieve)

发布于:2025-02-10 ⋅ 阅读:(81) ⋅ 点赞:(0)

测试题目:AcWing 868. 筛质数

埃氏筛(Sieve of Eratosthenes)

如果 i i i是素数,每次把 i i i的倍数都筛掉,存在重复筛选,时间复杂度 n ⋅ l o g ( l o g n ) n \cdot log(logn) nlog(logn)

#include <iostream>

using namespace std;

const int N = 1e6 + 10;
int n, cnt = 0;
bool st[N]; // false as prime

int main()
{
    cin >> n;
    for (int i = 2; i <= n; i ++ )
    {
        if (st[i]) continue;
        cnt ++ ;
        for (int j = i * 2; j <= n; j = j + i)
            st[j] = true;
    }
    cout << cnt << endl;
    
    return 0;
}

线性筛(Linear Sieve)

也叫欧拉筛,在埃氏筛的思想下,想办法让每个合数只被筛出去一次,消除重复筛选,这样时间复杂度就能降低到 O ( n ) O(n) O(n)

为了让每个合数 a a a只被筛出去一次,由于我们是从小到大筛选质数的,因此可以考虑让这个合数 a = a 1 ⋅ a 2 ⋅ . . . ⋅ a k a = a_1 \cdot a_2 \cdot ... \cdot a_k a=a1a2...ak由其最小的质因数 a 1 a_1 a1筛掉。

因此每次遍历到一个数 i i i,不论是质数或者合数,其最小质因数如果是 r r r,那么由于我们从小到大筛到了 i i i,所以质数 r r r一定已经在目前的质数结果集里了(被筛好了)。

进而,所有 < r <r <r的质数都已经被筛好了,我们可以对于每个 < = r <=r <=r的质数 x x x,把 x ⋅ i x \cdot i xi筛掉,那么因为 x ⋅ i x \cdot i xi的最小质因数一定是 x x x,所以理应在(且仅在)这一轮被筛掉。

然而,知道每个数 i i i的最小质因数 r r r是困难的,但反正我们都要拿它每个 < = r <=r <=r的质数 x x x去筛掉 x ⋅ i x \cdot i xi了,每次筛掉之后看一下质数 x x x是不是 i i i的因数就可以了,因为我们是从小到大遍历质数 x x x的,所以 x x x第一次成为 i i i的因数的时候, x x x就是 i i i的最小质因数,这时候就可以停止筛了。

如果没有停止筛会有什么问题?重复筛选导致的算法退化!试想应在 x x x i i i的因子时停止,最后一轮筛掉的就是 x ⋅ i x \cdot i xi,如果没有停下来,又取了下一个质数 x ′ x' x,筛掉了 x ′ ⋅ i x' \cdot i xi。因为 i i i的最小质因数就是 x x x,所以 x ′ ⋅ i x' \cdot i xi这个数应该在先前就被质数 x x x x ⋅ i ⋅ x ′ x x \cdot \frac{i \cdot x'}{x} xxix的模式筛过了!

写法上应该注意,线性筛不是像埃氏筛那样,只在发现质数的轮次筛合数,而是在每个轮次 i i i,不管最小质因数是 r r r的当前轮次 i i i是不是质数,都用 < = r <=r <=r的所有质数 x x x去筛 x ⋅ i x \cdot i xi,以保证每个数都仅被其最小质因数筛掉。

#include <iostream>

using namespace std;

const int N = 1e6 + 10;
int primes[N];
int n, cnt = 0;
bool st[N]; // false as prime

int main()
{
    cin >> n;
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i]) primes[cnt ++ ] = i;
        for (int j = 0; primes[j] * i <= n; j ++ )
        {
            int x = primes[j];
            st[x * i] = true;
            if (i % x == 0) break;
        }
    }
    cout << cnt << endl;
    
    return 0;
}

网站公告

今日签到

点亮在社区的每一天
去签到