• 素数筛法


    素数筛法是数论的入门,当然也非常重要。所谓素数(也叫质数),就是因数只有1和它本身的数。

    今天讲一下怎么筛素数。

    第一种算法,就是最朴素最暴力的算法,是人的都会。就是对于每一个数n,从 i = 2开始,依次判断n能否被i整除。

     1 bool judge(int x)
     2 {
     3     for(int i = 2; i * i <= x; ++i)        //只用判断到sqrt(n)就行 
     4         if(x % i == 0) return false;
     5     return true;
     6 }
     7 void pusushaifa(int n)
     8 {
     9     for(int i = 2; i <= n; ++i)
    10         if(judge(i)) printf("%d ", i);
    11     printf("
    ");    
    12 }

    好想的当然也就慢,这个算法复杂度是O(nlogn),当n为1e7 时就过不了了。

     

    第二种算法,Eratosthenes 筛法,简称埃氏筛法。这个算法也很好理解:对于不超过 n 的非负整数 p,删除2p, 3p, 4p……,当处理完所有数据后,没被删除的就是素数。

    用 vis[i] = 1 表示 i 被删除,代码就可以写出来了

     1 const int maxn = 1e7 + 5;
     2 int vis[maxn];
     3 void erato1(int n)
     4 {
     5     memset(vis, 0, sizeof(vis));
     6     for(int i = 2; i <= n; ++i)
     7         for(int j = 2 * i; j <= n; j += i) vis[j] = 1;
     8     for(int i = 2; i <= n; ++i) 
     9         if(!vis[i]) printf("%d ", i); 
    10     printf("
    ");
    11 }

    我们分析一下复杂度:内层循环的次数是 (floor) n / i - 1 < n / i,所以内层循环的总次数一定小于 n / 2 + n / 3 + n / 4 +……+ n / n。因为 n / 2 + n / 3 < n,n / 4 + n / 5 + n / 6 + n / 7 < n,…… ,所以时间复杂度小于 O(nlogn)。

    这个算法还可以优化,“对于不超过 n 的非负整数 p”,p可以限定为素数,而且内层循环必不从 i * 2 开始,因为他已在 i = 2 时就筛掉了。

     1 const int maxn = 1e7 + 5;
     2 int vis[maxn];
     3 void erato2(int n)
     4 {
     5     memset(vis, 0, sizeof(vis));
     6     for(int i = 2; i * i <= n; ++i) if(!vis[i])      //是素数
     7         for(int j = i * i; j <= n; j += i) vis[j] = 1;
     8     for(int i = 2; i <= n; ++i) 
     9         if(!vis[i]) printf("%d ", i); 
    10     printf("
    ");
    11 }

    改进后的复杂度就变成了 O(nloglogn)。

    还有第三种一个很牛的算法,线性筛法。我们先回顾一下埃氏,埃氏算法的一个弊端就是一个数 n 被它的所有素因子筛了一遍,重复筛导致浪费时间。

    我们如何保证每一个数只被筛一遍呢?有一种想法,只要让每一个数只被他的最小素因子筛去就行。

    写代码时,开一个数组标记,和上面的埃氏筛法的数组相同,再开一个数组记录素数,用来表示某一个数的最小素因子。代码如下

     1 int notPrime[maxn], prime[maxn / 10];
     2 void xianxingshai(int n)
     3 {
     4     for(int i = 2; i <= n; ++i)
     5     {
     6         if(!notPrime[i]) {prime[++prime[0]] = i; printf("%d ", i);}    //如果是素数,就存下来 
     7         for(int j = 1; j <= prime[0] && prime[j] * i <= n; ++j)    //prime[j] * i就表示要筛的那个数 
     8         {
     9             notPrime[prime[j] * i] = prime[j];    //标记 
    10             if(i % prime[j] == 0) break;         
    11         }
    12     }
    13     printf("
    ");
    14 }

    第9行拿prime[j]标记,因为素数队列是严格递增的,因此notPrime[j] 表示的也是 j 的最小素因子。

    特别强调的是第10行,若 i 能被prime[j] 整除,则 i = prime[j] * x,所以 i * prime[j + m] = prime[j] * x * prime[j + m],因此 i * prime[j + m] 一定被 prime[j] 筛去,就不必再重复筛了。

    还有的就是,这个 i 一定是一个素数,因为如果是一个合数的话,那么他一定等于一个小于他的素数乘以另一个数,而这个素数一定在他之前就被遍历到了。

    因为每一个数只被筛去一次,所以时间复杂度就是 O(n)。

    特别鸣谢:http://www.cnblogs.com/suno/archive/2008/02/04/1064368.html,线性筛法这讲得很透。

  • 相关阅读:
    hdu1421 搬寝室(dp)
    HDU 2577(DP)
    扩展欧几里德算法
    unique函数的作用
    区间更新 zoj3911
    set的应用
    vue 事件处理器
    vue Class与style绑定
    vue的计算属性
    sass入门
  • 原文地址:https://www.cnblogs.com/mrclr/p/8470078.html
Copyright © 2020-2023  润新知