筛法

liu_jiangwen

概述

  筛法是一个在处理与素数有关的问题时经常使用到的方法。如果只需要对一个整数进行素性测试,那么通常使用 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 ,在i=2的时候会由于 isPrim[j=i3=6]=false i s P r i m [ j = i ∗ 3 = 6 ] = f a l s e 将其筛去一次;在 i=3 i = 3 的时候,会由于 isPrim[j=i2=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 为素数的时候,我们将其添加到primes数组中,随后,无论 i i 是否是素数,我们都令temp=iprimes[j],显然, temp t e m p 为合数。
接下来,就是使得欧拉筛为线性的关键所在:

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

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

(1)n=p1α1p2α2p3α3pkαk(pipipi+11αi)
  而上述代码的字面意思为:当 i i 可以被primes[j]整除(即 i i 含有质因子primes[j])的时候终止内层循环。
  现在我们将 i i 表示为(1)的形式,即 i=pα11pα22pα33pα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 中的元素以及数列{ p1p1p3pn p 1 p 1 p 3 … p n }都是从小到大排列的,所以若 primes[j]=pss>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 ,依旧将其表示为(1)的形式, x=pα11pα22pα33pα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α111pα22pα33pα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];
            }
        }
    }
}
  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值