概述
筛法是一个在处理与素数有关的问题时经常使用到的方法。如果只需要对一个整数进行素性测试,那么通常使用
的算法就够了,但是如果需要对某个区间中的整数进行素性测试,那么就需要使用到筛法了。
下面首先介绍易于理解的埃氏筛法,它的时间复杂度为
(这个时间复杂度我也不知道是怎么算出来的,反正《挑战程序设计竞赛》上面是这么说的),再介绍时间复杂度为
的快速筛法。
诶氏筛法(埃拉托斯特尼筛法)
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;
}
}
}
}
通过观察可以发现,对于同一个数,诶氏筛法有可能会重复将其筛去。比如说对于 ,在 的时候会由于 将其筛去一次;在 的时候,会由于 再次将其筛去。因此,诶氏筛法的时间复杂度无法达到线性的 。
欧拉筛(线性筛)
相比于简单易懂诶氏筛法,欧拉筛在理解上面会有些困难,但是它对于同一个数,它并不会将其重复筛去,因此,它的时间复杂度几乎可以说是真正的做到了 的程度。同时,它还可以进行扩展,用来计算欧拉函数和莫比乌斯函数。
使用欧拉筛筛选素数
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;
}
}
}
}
那么这段代码是怎么实现将
中的每个数字都筛选到并且只筛选一次的呢?
当
为素数的时候,我们将其添加到
数组中,随后,无论
是否是素数,我们都令
,显然,
为合数。
接下来,就是使得欧拉筛为线性的关键所在:
if(i % primes[j] == 0) {
break;
}
首先,我们知道,每一个大于等于2的数字 都可以表示成
现在我们将 表示为 的形式,即 ,显然,若 ,则 ,理由如下:
由于数组 中的元素以及数列{ }都是从小到大排列的,所以若 ,则在数组 中必然存在元素 使得 ,于是内层循环在执行到 时便满足跳出循环的条件,无法执行到 这一步。
对于任意合数 ,依旧将其表示为 的形式, , 。显然,根据以上分析,只有在 的时候才会将 筛去。因此,对于任意合数,欧拉筛都会且只会筛去其一次。
使用欧拉筛计算欧拉函数
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];
}
}
}
}