筛法

liu_jiangwen

概述

  筛法是一个在处理与素数有关的问题时经常使用到的方法。如果只需要对一个整数进行素性测试,那么通常使用 O ( n ) 的算法就够了,但是如果需要对某个区间中的整数进行素性测试,那么就需要使用到筛法了。
  下面首先介绍易于理解的埃氏筛法,它的时间复杂度为 O ( n l o g l o g 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 ,在 i = 2 的时候会由于 i s P r i m [ j = i 3 = 6 ] = f a l s e 将其筛去一次;在 i = 3 的时候,会由于 i s P r i m [ j = i 2 = 6 ] = f a l s e 再次将其筛去。因此,诶氏筛法的时间复杂度无法达到线性的 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 ) 中的每个数字都筛选到并且只筛选一次的呢?
i 为素数的时候,我们将其添加到 p r i m e s 数组中,随后,无论 i 是否是素数,我们都令 t e m p = i p r i m e s [ j ] ,显然, t e m p 为合数。
接下来,就是使得欧拉筛为线性的关键所在:

if(i % primes[j] == 0) {
    break;
}

  首先,我们知道,每一个大于等于2的数字 n 都可以表示成

(1) n = p 1 α 1 p 2 α 2 p 3 α 3 p k α k ( p i p i p i + 1 1 α i )
  而上述代码的字面意思为:当 i 可以被 p r i m e s [ j ] 整除(即 i 含有质因子 p r i m e s [ j ] )的时候终止内层循环。
  现在我们将 i 表示为 ( 1 ) 的形式,即 i = p 1 α 1 p 2 α 2 p 3 α 3 p k α k ,显然,若 i | p r i m e s [ j ] ,则 p r i m e s [ j ] = p 1 ,理由如下:

  由于数组 p r i m e s 中的元素以及数列{ p 1 p 1 p 3 p n }都是从小到大排列的,所以若 p r i m e s [ j ] = p s s > 1 ,则在数组 p r i m e s 中必然存在元素 p r i m e s [ j ] ( j < j ) 使得 p r i m e s [ j ] = p 1 ,于是内层循环在执行到 j = j 时便满足跳出循环的条件,无法执行到 p r i m e s [ j ] = p s 这一步。

  对于任意合数 x ,依旧将其表示为 ( 1 ) 的形式, x = p 1 α 1 p 2 α 2 p 3 α 3 p k α k ( i = 1 k α i > 1 ) 。显然,根据以上分析,只有在 i = p 1 α 1 1 p 2 α 2 p 3 α 3 p k α k 的时候才会将 x 筛去。因此,对于任意合数,欧拉筛都会且只会筛去其一次。

使用欧拉筛计算欧拉函数

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];
            }
        }
    }
}

猜你喜欢

转载自blog.csdn.net/liu_jiangwen/article/details/80793349