什么是筛法?

筛法的直接用途是用来解决寻找$1~N$中的质数的问题的。
也常用来间接解决一些其他的数论问题。

最简单的筛法:艾氏筛法

简介

艾氏筛法是比较直接就能想到的一种筛法。
每找到一个质数,就用它来筛掉后面所有它的倍数。
这样每找到一个没有被筛掉的数,由于前面所有比他小的数都不是它的因数,所以它一定是一个质数。

代码

代码也很容易实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <vector>
#include <cmath>

auto eratosthenes(int upperbound)
{
std::vector<bool> flag(upperbound + 1, true);
flag[0] = flag[1] = false; //exclude 0 and 1
for (int i = 2; i <= sqrt(upperbound); ++i)
{
if (flag[i])
{
for (int j = i * i; j <= upperbound; j += i)
{
flag[j] = false;
}
}
}
return flag;
}

它的复杂度是$O(nloglogn)$。

欧拉筛法

简介

上面的筛法虽然复杂度已经很低了,但是我们仍然需要一种更加快速的筛法。

很明显,我们上一个算法不够迅速的原因是有的数字被筛了多次。如果能避免这种情况,我们的算法就会更快。

欧拉筛法可以保证范围内的每个合数都被删掉(在 bool 数组里面标记为非素数),而且任一合数只被:

“最小质因数 × 最大因数(非自己) = 这个合数”

的途径删掉。由于每个数只被筛一次,时间复杂度为 O(n)。

代码

先上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <cstdio>
#include <cstring>

bool isPrime[100000010];
//isPrime[i] == 1表示:i是素数
int Prime[6000010], cnt = 0;
//Prime存质数

void GetPrime(int n)//筛到n
{
memset(isPrime, 1, sizeof(isPrime));
//以“每个数都是素数”为初始状态,逐个删去
isPrime[1] = 0;//1不是素数
for(int i = 2; i <= n; i++)
{
if(isPrime[i])//没筛掉
Prime[++cnt] = i; //i成为下一个素数
for(int j = 1; j <= cnt && i*Prime[j] <= n/*不超上限*/; j++)
{
//从Prime[1],即最小质数2开始,逐个枚举已知的质数,并期望Prime[j]是(i*Prime[j])的最小质因数
//当然,i肯定比Prime[j]大,因为Prime[j]是在i之前得出的
isPrime[i*Prime[j]] = 0;
if(i % Prime[j] == 0)//i中也含有Prime[j]这个因子
break; //重要步骤。见原理
}
}
}

int main()
{
int n, q;
scanf("%d %d", &n, &q);
GetPrime(n);
while (q--)
{
int k;
scanf("%d", &k);
printf("%d\n", Prime[k]);
}
return 0;
}

解释

容易发现,这份代码和刚才的那种筛法主要的区别就在于

1
2
if(i % Prime[j] == 0)
break;

为什么?

刚才说到过,我们只想让一个数的最大因数筛掉这个数。

模拟一下这个程序:

当前用于筛素数的数i 被i筛掉的数 被i筛掉的数 被i筛掉的数 被i筛掉的数
2 2 * 2=4
3 3 * 2=6 3 * 3=9
4 4 * 2=8
5 5 * 2=10 5 * 3=15 5 * 5=25
6 6 * 2=12
7 7 * 2=14 7 * 3=21 7 * 5=35 7 * 7=49
8 8 * 2=16
9 9 * 2=18 9 * 3=27
10 10 * 2=20

这就达到了预期,让一个数的最大因数筛掉这个数。

为什么呢?

素数表prime中已有两个素数:2和3。

此时在check数组中已经被标记为1的数(合数)是:4、6、9。

因为$i=4$已经被标记为1,所以我们不将4添加进素数表prime

此时执行 j 循环:

因为素数表prime中有2和3,所以预计将要被筛掉的数是$4×2=8$ 和 $4×3=12$。

当$4×2=8$被筛掉以后,经过 $i%prime[j]=0$ 判断可以知道$4%2=0$, 此时应结束 j 循环,不再筛 $4×3=12$。

我们都知道12的最大因数(非自身,下同)是6,因此12应该被6筛掉,那么上述判断的原理是什么呢?

因为当出现 $i%prime[j]=0$ 的情况时,意味着$prime[j]$ 是 i 的一个因数(2是4的一个因数)。

既然4是12的因数,2又是4的因数,所以2也是12的因数。

由此可以知道,4不是12的最大因数,12的最大因数会在后面出现,即2所对应的6,12将会在i=6时被筛掉。

同理可以判断:9是45的因数,3又是9的因数,所以3也是45的因数,9不是45的最大因数,45的最大因数将会在后面出现,即3所对应的15,45将会在$i=15$时被筛掉。