筛质数
质数也称素数。依整除性定义:素数只能被常数1或自己整除,不能被常数1或自己以外的其他数整除,这种正整数称为素数。
与之相对的是合数,合数是指在大于1的整数中除了能被1和本身整除外,还能被其他数(0除外)整除的数。
要求:筛选出n
以内的所有质数。
埃氏筛
把每个数的倍数标记,被标记的数显然是合数。
没有被标记的就是质数。因为对于一个数n
,他没有被标记,说明他不是2
到n-1
间任何数的倍数,所以他是质数。
代码1
朴素筛,对每一个数筛除其倍数。
cnt = 0; // 质数的数量
for(int i = 2; i <= n; i ++) {
if(!a[i]) // a[i] = false 代表i是质数
primes[cnt++] = i;
for(int j = i + i; j <= n; j += i) // 2i, 3i, ...
a[j] = true; // a[j] = true 代表数j被标记,即不是质数
}
代码在i=2时循环筛 n 2 \frac{n}{2} 2n次,i=3时循环筛 n 3 \frac{n}{3} 3n次…即 n 2 + n 3 + . . . + n n = n ( 1 2 + 1 3 + . . . + 1 n ) \frac{n}{2}+\frac{n}{3}+...+\frac{n}{n} =n(\frac{1}{2}+\frac{1}{3}+...+\frac{1}{n}) 2n+3n+...+nn=n(21+31+...+n1)。括号内为调和级数,约为ln n + C。即代码1时间复杂度约为O(n ln n) < O(n log n)
代码2
只需要对质数筛倍数,因为合数已经被质数标记过了。
根据整数唯一分解定理,任何一个大于1的整数
n
都可以分解成若干个素因数的连乘积,如果不计各个素因数的顺序,那么这种分解是唯一的。也就是说,对合数x来说,他会被质数筛掉;同理,对于x的倍数来说,他也会被质数筛掉。
所以不需要对合数筛其倍数。
for(int i = 2; i <= n; i ++) {
if(!a[i]) {
primes[cnt++] = i;
for(int j = i + i; j <= n; j += i) // 2i, 3i, ...
a[j] = true;
}
}
根据质数定理,1~n
中有
n
l
n
n
\frac{n}{ln \ n}
ln nn个质数。对每个质数x
,需要筛n/x
次,总复杂度约为
n
l
n
n
×
l
n
n
=
n
\frac{n}{ln \ n} \times ln \ n = n
ln nn×ln n=n,实际为O(n log log n)。
代码3
上述代码2中核心代码可以再优化一些些,因为他是从i*2
开始枚举,会有一些重复计算。
for(int j = i + i; j <= n; j += i) // i*2, i*3, ...
a[j] = true;
2x2 2x3 2x4 2x5 2x6
3x2 3x3 3x4 3x5 3x6 // 3x2 在 上一行 2x3 中已经算过了
5x2 5x3 5x4 5x5 5x6 // 5x2 和 5x3 在上两行已经算过了
...
所以,改成从i*i
开始枚举即可。
// 注意尽量不要写成 i * j <= n ,因为i * j可能因为过大而越界为负数,导致下标错误
for(int j = i; i <= n / j; j ++) // i*i, i*(i+1), ...
a[i * j] = true;
线性筛
代码2中会有重复筛,例如质数2和3都会筛6,12,…。
线性筛中,数n只会被最小质因子筛一次,时间复杂度约为O(n)
。
代码
从小到大枚举所有的质数。
当i % primes[j] == 0
,primes[j]
一定是i
的最小质因子,primes[j]
也一定是primes[j]*i
的最小质因子。
当i % primes[j] != 0
,primes[j]
一定小于i
的所有质因子,primes[j]
也一定是primes[j]*i
的最小质因子。
不需要j < cnt && primes[j] * i <= n
。因为如果i
是合数,当primes[j]
是i
的最小质因子时会结束;如果i
是质数,primes[j] == i
时也会结束。
if(!a[i])
primes[cnt++] = i;
for(int j = 0; primes[j] * i <= n; j ++) {
a[primes[j] * i] = true;
if(i % primes[j] == 0)
break;
}