素数筛
质数筛选
质数筛选指在一个范围内筛去非质数,留下质数,通常用单独的质数数组保存留下的质数。
埃氏筛
对于每个数字(从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;
}