素数筛法
埃氏筛法
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=p1n1p2n2⋯pknk
那么他就会在筛
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=k∗p 的形式.
一个自然的想法是我们此时枚举最小质因子 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 k∗p 的最小质因子必定不是 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=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)
初步来看
k
k
k 需要枚举 2 到 n 内的所有数,而由前面知
k
k
k 的任意一个质因子均不小于
p
p
p , 即至少
k
≥
p
k\ge p
k≥p。
且对于一个固定的
k
k
k 来说
p
p
p 不能大于
k
k
k 的最小质因子 ,否则
k
∗
p
k * p
k∗p 的最小质因子必定不是
p
p
p 。
对于每一个
k
k
k ,
p
p
p 从 2 不断枚举, 直到
k
∗
p
>
n
k*p>n
k∗p>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);
}
}
}