因数只有 1 1 1 和这个数本身的数被称作素数。
注意: 1 1 1 既不是素数也不是合数, 2 2 2 是最小的素数。
对于任意大于 1 1 1 的整数 x x x,都可以分解成若干个素数的乘积:
x = p 1 a 1 × p 2 a 2 × p 3 a 3 × ⋯ × p n a n ( a i ∈ Z + ) x=p_1^{a_1}\times p_2^{a_2}\times p_3^{a_3}\times \cdots \times p_n^{a_n}(a_i \in \Z^+) x=p1a1×p2a2×p3a3×⋯×pnan(ai∈Z+)
比如 57 = 3 × 19 57=3 \times19 57=3×19, 1704 = 2 3 × 3 × 71 ⋯ 1704=2^3 \times 3\times 71\cdots 1704=23×3×71⋯。
若存在正整数 a a a 与素数 p p p,且 gcd ( a , p ) = 1 \gcd(a,p)=1 gcd(a,p)=1。则有 a p − 1 ≡ 1 ( m o d p ) a^{p-1}\equiv 1\pmod p ap−1≡1(modp)。
证明就不给了,有兴趣可以上网找一下。费马小定理在后续会有很多用处。
试判断 x x x 是否为素数。
由素数的定义可知,素数的因数只有两个。所以我们可以直接暴力枚举除 1 1 1 以外小于 x x x 的数。
时间复杂度为 O ( n ) O(n) O(n),能不能优化呢?答案是可以的。
由因数的性质可知,若正整数 n n n 有一因数 i i i 且 i ≤ n i\le\sqrt{n} i≤n,则 n n n 必有另外一因数 j j j 且 j ≥ n j\ge\sqrt{n} j≥n。在 i = n i=\sqrt{n} i=n 时,有 i = j i=j i=j。
根据这一个性质,我们可以只枚举 2 2 2 到 x \sqrt{x} x 的数进行试除,时间复杂度优化到 O ( n ) O(\sqrt{n}) O(n)。
inline bool is_prime(int x) {
for(register int i = 2;i <= sqrt(x);i ++)
if(x % i == 0)
return false;
return true;
}
但当题目要求求出一串数中的素数时,试除法的时间复杂度就变成了 O ( n n ) O(n\sqrt{n}) O(nn),这在 n n n 过大时是不能接受的。所以,我们便有了素数的筛法。
素数的第一种筛法。那何为筛法呢?顾名思义,就是把不是素数的数筛掉,这样剩下的就只有素数了。那怎么筛呢?
由唯一分解定理可知,任意一个大于 1 1 1 的合数都可以表示成多个素数相乘。此时不难想到,对于每一个素数,我们可以把它的所有的倍数筛掉,这样就可以把题目给定范围内的合数都筛掉,只留下素数。记得要特判 1 1 1,因为它既不是素数也不是合数。这样我们便有了素数的第一种筛法。鉴于这个筛法是古希腊数学家埃拉托斯特尼发明的,所以我们便称这个筛法为埃氏筛。
给大家模拟一下,加深理解:
此时,若我们要筛出 12 12 12 以内的素数。( f a l s e false false 表示是素数, t r u e true true 则反之)
1 1 1 | 2 2 2 | 3 3 3 | 4 4 4 | 5 5 5 | 6 6 6 | 7 7 7 | 8 8 8 | 9 9 9 | 10 10 10 | 11 11 11 | 12 12 12 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
f l a g flag flag | t r u e \color{red}true true | f a l s e false false | f a l s e false false | f a l s e false false | f a l s e false false | f a l s e false false | f a l s e false false | f a l s e false false | f a l s e false false | f a l s e false false | f a l s e false false | f a l s e false false |
1 1 1 | 2 2 2 | 3 3 3 | 4 4 4 | 5 5 5 | 6 6 6 | 7 7 7 | 8 8 8 | 9 9 9 | 10 10 10 | 11 11 11 | 12 12 12 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
f l a g flag flag | t r u e true true | f a l s e \color{orange}false false | f a l s e false false | t r u e \color{red}true true | f a l s e false false | t r u e \color{red}true true | f a l s e false false | t r u e \color{red}true true | f a l s e false false | t r u e \color{red}true true | f a l s e false false | t r u e \color{red}true true |
1 1 1 | 2 2 2 | 3 3 3 | 4 4 4 | 5 5 5 | 6 6 6 | 7 7 7 | 8 8 8 | 9 9 9 | 10 10 10 | 11 11 11 | 12 12 12 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
f l a g flag flag | t r u e true true | f a l s e false false | f a l s e \color{orange}false false | t r u e true true | f a l s e false false | t r u e \color{red}true true | f a l s e false false | t r u e true true | t r u e \color{red}true true | t r u e true true | f a l s e false false | t r u e \color{red}true true |
1 1 1 | 2 2 2 | 3 3 3 | 4 4 4 | 5 5 5 | 6 6 6 | 7 7 7 | 8 8 8 | 9 9 9 | 10 10 10 | 11 11 11 | 12 12 12 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
f l a g flag flag | t r u e true true | f a l s e false false | f a l s e false false | t r u e true true | f a l s e \color{orange}false false | t r u e true true | f a l s e false false | t r u e true true | t r u e true true | t r u e \color{red}true true | f a l s e false false | t r u e true true |
重新回看表格,所有的 f a l s e false false 即为素数:
1 1 1 | 2 2 2 | 3 3 3 | 4 4 4 | 5 5 5 | 6 6 6 | 7 7 7 | 8 8 8 | 9 9 9 | 10 10 10 | 11 11 11 | 12 12 12 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
f l a g flag flag | t r u e true true | f a l s e \color{red}false false | f a l s e \color{red}false false | t r u e true true | f a l s e \color{red}false false | t r u e true true | f a l s e \color{red}false false | t r u e true true | t r u e true true | t r u e true true | f a l s e \color{red}false false | t r u e true true |
int n;
bool flag[MAXN];
void prime(int n) {
flag[1] = true;
for(register int i = 2;i <= n;i ++)
if(!flag[i])
for(register int j = 2;j * i <= n;j ++)
flag[j * i] = true;
for(register int i = 1;i <= n;i ++)
if(!flag[i])
cout << i << " ";
return;
}
埃氏筛的时间复杂度是: O ( n log log n ) O(n \log \log n) O(nloglogn)。由于证明过程过于繁琐,这里就不赘述了,有兴趣可以去搜一下。一般来说,知道时间复杂度就可以了。
埃氏筛的时间复杂度已经很优了,但当题目的数据范围达到 1 0 9 10^9 109 时,埃氏筛便处理不了了。此时,我们便需要用到线性筛了。
顾名思义,线性筛的时间复杂度在埃氏筛上的基础上优化到了 O ( n ) O(n) O(n)。具体优化在哪里?
不难发现,许多正整数都有着超过 1 1 1 个的素因数。而在埃氏筛中,正整数会被其所有的素因数都筛一遍,这里浪费了很多时间。但其实,我们只需要用每个正整数最小的素因数去筛便可以了,线性筛就在这个点上优化了。
拿刚刚的例子讲解一下:
在小于 12 12 12 的数中有 6 = 2 × 3 , 10 = 2 × 5 , 12 = 2 2 × 3 6=2\times 3,10=2\times5,12=2^2\times3 6=2×3,10=2×5,12=22×3。这三个数都被筛了多次。
1 1 1 | 2 2 2 | 3 3 3 | 4 4 4 | 5 5 5 | 6 6 6 | 7 7 7 | 8 8 8 | 9 9 9 | 10 10 10 | 11 11 11 | 12 12 12 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
f l a g flag flag | t r u e true true | f a l s e false false | f a l s e false false | t r u e true true | f a l s e false false | t r u e \color{red}true true | f a l s e false false | t r u e true true | t r u e true true | t r u e \color{red}true true | f a l s e false false | t r u e \color{red}true true |
而在线性筛中, 6 6 6, 10 10 10, 12 12 12,都只会被 2 2 2 筛掉 1 1 1 次。
具体的实现方法便是只用小于等于自己最小的素因数的素数来筛掉合数。
算法流程:
首先与埃氏筛一样,要特判 1 1 1 的情况。
然后,把找到的素数都放进一个 prime 数组里面。假设我们现筛到了 i i i,则我们便会用 prime 数组中的素数与 i i i 相乘,把相乘所得到的结果都标记为合数。当 i i i 除以 prime 中的素数余数为 0 0 0 时停止(先筛完再停止)。这样便做到了用最小的素数筛掉合数。
为什么这是正确的呢?我们来证明一下:
设 prime 数组中的素数为 k 1 k_1 k1, k 2 k_2 k2, ⋯ \cdots ⋯, k n k_n kn,且 k 1 ≤ k 2 ≤ ⋯ ≤ k n k_1\le k_2 \le \cdots \le k_n k1≤k2≤⋯≤kn。
若 i i i 为素数。
当 k i ∤ i k_i\nmid i ki∤i 时说明 k i k_i ki 比 i i i 小,所以 k i k_i ki 是正整数 k i × i k_i \times i ki×i 最小的素因数。
当 k i ∣ i k_i \mid i ki∣i 时说明 k i = i k_i=i ki=i,此时, k i k_i ki 与 i i i 同为 k i × i k_i \times i ki×i 的最小素因数。
若 i i i 不为素数,设其分解素因数形式为: p 1 a 1 × p 2 a 2 × ⋯ × p n a n p_1^{a_1}\times p_2^{a_2} \times \cdots \times p_n^{a_n} p1a1×p2a2×⋯×pnan。
则当 k i ∤ i k_i \nmid i ki∤i 时,同样的, k i k_i ki 必然比 p 1 p_1 p1 要小,所以 k i k_i ki 肯定是正整数 k i × i k_i \times i ki×i 的最小素因数。
当 k i ∣ i k_i \mid i ki∣i 且是第一次整除时说明 k i = p 1 k_i=p_1 ki=p1,此时, k i k_i ki 与 p 1 p_1 p1 同为 k i × i k_i \times i ki×i 的最小素因数。
int prime[MAXN] , cnt;
bool is_prime[MAXN];
inline void Init(int x) {
is_prime[1] = true;
for(register int i = 2;i <= x;i ++) {
if(!is_prime[i])
prime[++ cnt] = i;
for(register int j = 1;j <= cnt && i * j <= x;j ++) {
is_prime[i * prime[j]] = true;
if(i % prime[j])
break;
}
}
return;
}