素数筛

质数筛选

质数筛选指在一个范围内筛去非质数,留下质数,通常用单独的质数数组保存留下的质数。

埃氏筛

对于每个数字(从2开始),筛去它所有的倍数,那么留下来的一定都是质数。

证:对于任意和数 \(x\) ,一定存在一个质因数 \(pj\) ,那么我们一定可以用 \(pj\) 筛去 \(x\)

时间复杂度 \(O (n log n)\)

证:对于每个数字 \(i\) ,内层循环会执行 \(n / i\) 次。那么一共执行

\[n / 2 + n / 3 + ... n / n = n(1 / 2 + 1 / 3 + ... + 1 / n) = n log n \]

​ 次。

bool st[N];
int prime[N], cnt;

for (int i = 2; i <= n; i ++ )
{
    if (!st[i]) prime[++ cnt] = i;
    for (int j = i + i; j <= n; j += i ) st[j] = true;
}

我们发现,对于每个和数 \(x\) ,假设存在质因数 \(pj\) ,那么如果我们用和数 \(x\) 来筛去的数字也一定存在质因数 \(pj\) ,也就是在 \(x\) 之前这些数字就已经被 \(pj\) 筛去,不需要再用 \(x\) 筛去。

优化版:

for (int i = 2; i <= n; i ++ )
{
    if (!st[i])
    {
        prime[++ cnt] = i;
        for (int j = i + i; j <= n; j += i ) st[j] = true;
    }
}

质数定理: \(1 \sim n\) 中大约有 \(n \ / \ ln \ n\) 个质数,那么总执行次数大约为 \(n log n / log n = n\) ,实际复杂度大约为 \(O(n log log n)\)

欧拉筛

又称线性筛,每次我们都只用最小质因数来筛去和数 \(x\) ,这样就不会重复筛数。因此时间复杂度为 \(O(n)\)

原理写在代码块中,例题:

[洛谷3383]:

https://www.luogu.com.cn/problem/P3383

#include <bits/stdc++.h>

using namespace std;

const int N = 100000010;

int n;
int prime[N], idx;
bool st[N];

void init ()
{
    scanf("%d", &n);
    // O(n) 线性筛
    for (int i = 2; i <= n; i ++ )
    {
        // 没有标记,是质数
        if (!st[i]) prime[++ idx] = i;
        /*
            由于被筛选的值 k 可能含有多个质因数,会被筛去多次。
            我们只用k的最小质因数筛去k,这样每个数字都只会被筛去一次

            k = prime[j] * i      i是当前数
            从小到大枚举所有质数
            1. i % prime[j] == 0  prime[j]是i的最小质因数,那么prime[j]一定是k的最小质因数
            2. i % prime[j] != 0  prime[j]小于i的任意质因数,那么prime[j]一定也是k的最小质因数

            对于每个和数x,一定存在最小质因数pj,那么当我们枚举到x / pj时,就可以把x筛去
        */
        for (int j = 1; prime[j] <= n / i; j ++ )
        {
            st[i * prime[j]] = true;
            if (i % prime[j] == 0) break;
            /*
                为什么这里需要break?
                假设当前被筛去的数字是 i * pj,由于i是pj的倍数,那么如果继续递增pj,i的最小质因数
                一定还是原来的pj,被筛去的数字最小质因数也一定是pj,那么我们再筛的话就会产生重复
            */
        }
    }
}

void solve ()
{
    int k; scanf("%d", &k);
    printf("%d\n", prime[k]);
}

signed main () 
{
    init();

    int T; scanf("%d", &T); while (T -- )
        solve();

    return 0;
}
posted @ 2021-11-20 14:19  Horb7  阅读(40)  评论(0编辑  收藏  举报