概述
筛法是一个在处理与素数有关的问题时经常使用到的方法。如果只需要对一个整数进行素性测试,那么通常使用
O(n−−√)
O
(
n
)
的算法就够了,但是如果需要对某个区间中的整数进行素性测试,那么就需要使用到筛法了。
下面首先介绍易于理解的埃氏筛法,它的时间复杂度为
O(nloglogn)
O
(
n
l
o
g
l
o
g
n
)
(这个时间复杂度我也不知道是怎么算出来的,反正《挑战程序设计竞赛》上面是这么说的),再介绍时间复杂度为
O(n)
O
(
n
)
的快速筛法。
诶氏筛法(埃拉托斯特尼筛法)
const int maxn = 100000;
bool isPrim[maxn];
int primes[maxn];
int countPrm;
void sieve() {
memset(isPrim, -1, sizeof(isPrim));
countPrim = 0;
isPrim[1] = false;
for(int i = 2; i < maxn; i++) {
if(isPrim[i]) {
primes[countPrim++] = i;
for(int j = i; j < maxn; j += i) {
isPrim[j] = false;
}
}
}
}
通过观察可以发现,对于同一个数,诶氏筛法有可能会重复将其筛去。比如说对于 6 6 ,在的时候会由于 isPrim[j=i∗3=6]=false i s P r i m [ j = i ∗ 3 = 6 ] = f a l s e 将其筛去一次;在 i=3 i = 3 的时候,会由于 isPrim[j=i∗2=6]=false i s P r i m [ j = i ∗ 2 = 6 ] = f a l s e 再次将其筛去。因此,诶氏筛法的时间复杂度无法达到线性的 O(n) O ( n ) 。
欧拉筛(线性筛)
相比于简单易懂诶氏筛法,欧拉筛在理解上面会有些困难,但是它对于同一个数,它并不会将其重复筛去,因此,它的时间复杂度几乎可以说是真正的做到了 O(n) O ( n ) 的程度。同时,它还可以进行扩展,用来计算欧拉函数和莫比乌斯函数。
使用欧拉筛筛选素数
const int maxn = 100000;
bool isPrim[maxn];
int primes[maxn];
int countPrm;
void sieve() {
int temp;
memset(isPrim, -1, sizeof(isPrim));
isPrim[1] = false;
countPrim = 0;
for(int i = 2; i < maxn; i++) {
if(isPrim[i]) {
primes[countPrim++] = i;
}
for(int j = 0; j < countPrim && (temp = i * primes[j]) < maxn; j++) {
isPrim[temp] = false;
if(i % primes[j] == 0) {
break;
}
}
}
}
那么这段代码是怎么实现将
[1,n)
[
1
,
n
)
中的每个数字都筛选到并且只筛选一次的呢?
当
i
i
为素数的时候,我们将其添加到数组中,随后,无论
i
i
是否是素数,我们都令,显然,
temp
t
e
m
p
为合数。
接下来,就是使得欧拉筛为线性的关键所在:
if(i % primes[j] == 0) {
break;
}
首先,我们知道,每一个大于等于2的数字 n n 都可以表示成
现在我们将 i i 表示为的形式,即 i=pα11pα22pα33…pαkk i = p 1 α 1 p 2 α 2 p 3 α 3 … p k α k ,显然,若 i|primes[j] i | p r i m e s [ j ] ,则 primes[j]=p1 p r i m e s [ j ] = p 1 ,理由如下:
由于数组 primes p r i m e s 中的元素以及数列{ p1p1p3…pn p 1 p 1 p 3 … p n }都是从小到大排列的,所以若 primes[j]=ps且s>1 p r i m e s [ j ] = p s 且 s > 1 ,则在数组 primes p r i m e s 中必然存在元素 primes[j′](j′<j) p r i m e s [ j ′ ] ( j ′ < j ) 使得 primes[j′]=p1 p r i m e s [ j ′ ] = p 1 ,于是内层循环在执行到 j=j′ j = j ′ 时便满足跳出循环的条件,无法执行到 primes[j]=ps p r i m e s [ j ] = p s 这一步。
对于任意合数 x x ,依旧将其表示为的形式, x=pα11pα22pα33…pαkk x = p 1 α 1 p 2 α 2 p 3 α 3 … p k α k , (∑ki=1αi>1) ( ∑ i = 1 k α i > 1 ) 。显然,根据以上分析,只有在 i=pα1−11pα22pα33…pαkk i = p 1 α 1 − 1 p 2 α 2 p 3 α 3 … p k α k 的时候才会将 x x <script type="math/tex" id="MathJax-Element-82">x</script>筛去。因此,对于任意合数,欧拉筛都会且只会筛去其一次。
使用欧拉筛计算欧拉函数
const int maxn = 100000;
bool isPrim[maxn];
int primes[maxn];
int phi[maxn];
int countPrm;
void sieve() {
LL temp;
memset(isPrim, -1, sizeof(isPrim));
memset(phi, 0, sizeof(phi));
phi[1] = 1;
countPrim = 0;
for(int i = 2; i < maxn; i++) {
if(isPrim[i]) {
primes[countPrim++] = i;
phi[i] = i - 1;
}
for(int j = 0; j < countPrim && (temp = i * primes[j]) < maxn; j++) {
isPrim[temp] = false;
if(i % primes[j] == 0) {
phi[temp] = phi[i]*primes[j];
break;
}
else {
phi[temp] = phi[i]*phi[primes[j]];
}
}
}
}
使用欧拉筛计算莫比乌斯函数
const int maxn = 100000;
bool isPrim[maxn];
int primes[maxn];
int mu[maxn];
int countPrm;
void sieve() {
memset(isPrim, -1, sizeof(isPrim));
memset(mu, 0, sizeof(mu));
memset(primes, 0, sizeof(primes));
countPrim = 0;
isPrim[1] = false;
mu[1] = 1;
LL temp;
for(int i = 2; i < maxn; i++) {
if(isPrim[i]) {
primes[countPrim++] = i;
mu[i] = -1;
}
for(int j = 0; j < countPrim && (temp = i * primes[j]) < maxn; j++) {
isPrim[temp] = false;
if(i % primes[j] == 0) {
mu[temp] = 0;
break;
}
else {
mu[temp] = -mu[i];
}
}
}
}