zoukankan      html  css  js  c++  java
  • 素数筛

    筛选出从2~n的所有素数

    埃氏筛

    对2~n中的每个数k,筛去k在2~n中的每一个倍数,最后剩下的就是2~n的所有素数,因为相当于每一个数k都会被2~k - 1的所有数筛一遍,一旦k存在2~k - 1的约数,那么他必定会被筛掉,反之k一定不会被筛掉。

    注意:k是素数 <=> k不存在2~k - 1的约数,所以在埃氏筛中一定不可以出现一个数把自己筛掉的情况(即约数是不能包括数字k本身的)

    朴素版

    #include<iostream>
    using namespace std;
    
    const int N = 1000010;
    
    int st[N], primes[N];
    int cnt;
    int n;
    
    void get_primes(int n){
        for(int i = 2; i <= n; i ++){
            if(!st[i]) primes[cnt ++] = i;
            for(int j = i + i; j <= n; j += i) st[j] = 1;
        }
    }
    
    int main(){
        cin >> n;
        
        get_primes(n);
        
        cout << cnt;
    }
    

    复杂度:

    (frac{n}{2} + frac{n}{3} +...+frac{n}{n})

    (=n(frac{1}{2} + frac{1}{3} +...+frac{1}{n}))

    (=n(ln(n + 1) -1 + delta))

    (approx nln(n) lt nlog_2n)

    所以复杂度计为(O(nlogn))

    优化版

    和朴素版的唯一区别:朴素版用所有的数筛,优化版用素数来筛

    k为素数 <=> k不存在2~k - 1中的质因数,即没有必要用2~ k - 1中的所有数来筛k,只需要用2~k - 1中的质数来筛k。

    因为如果一个数k有约数p,那么k会被p筛掉,由于k的倍数也一定是p的倍数,所以k的所有倍数也会被p筛掉,所以没有必要用k再筛一遍。

    注意:优化版依然存在重复筛的情况,因为一个数可能不止一个质因子,所以还是会重复筛。

    #include<iostream>
    using namespace std;
    
    const int N = 1000010;
    
    int st[N], primes[N];
    int cnt;
    int n;
    
    void get_primes(int n){
        for(int i = 2; i <= n; i ++){
            if(!st[i]){
                primes[cnt ++] = i;
                for(int j = i + i; j <= n; j += i) st[j] = 1;
            } 
        }
    }
    
    int main(){
        cin >> n;
        
        get_primes(n);
        
        cout << cnt;
    }
    

    复杂度:

    真实复杂度:(O(nloglogn))

    参照朴素版,优化版相当于只计算了(frac{n}{2} + frac{n}{3} +...+frac{n}{n})分母为素数的部分,又由质数定理(不超过n的素数个数为n/ln(n)),可得近似的时间复杂度为(O(n) ,(nln(n)/ln(n) = n)),这是错的,但是和真实复杂度很接近。

    线性筛

    保证了不重复筛的一种筛法。一个数用他的最小质因子筛,因为每个数的最小质因子唯一,所以每一个数都只会被筛一次。

    比如:12 = 2 * 2 * 3,他只会被2筛掉一次,而不会再被3重复筛。

    如果12被3筛,意味着一定是用4*3来筛的,但是4根本不会用3来筛,因为4只会用小于等于他的最小质因子的数和4相乘来筛别的数。

    void get_primes(int n){
        for(int i = 2; i <= n; i ++){
            if(!st[i]) primes[cnt ++] = i;
            
            //和埃氏筛的区别在这里
            for(int j = 0; primes[j] <= n / i; j ++){ //primes[j] <= n / i;是为了不越界,只筛n以内的合数
                st[primes[j] * i] = 1;
                if(i % primes[j] == 0) break;
            }
            
            
        }
    }
    

    首先j从小到大枚举素数,那么枚举到的质因子一定是最小的

    1. i % primes[j] != 0时,primes[j]一定小于i的所有质因子,那么primes[j]一定是primes[j] * i的最小质因子,把primes[j] * i给筛掉是对的。
    2. i % primes[j] == 0时,primes[j]是i的最小质因子,同样也是primes[j] * i的最小质因子,所以把primes[j] * i给筛掉也是对的。

    由1和2可知,算法确实是在用最小质因子来筛掉合数。

    现在证明一个合数一定会被筛并且只会被筛一次

    1)一个合数一定会被筛

    设x为2~n的一个合数,那么x一定存在一个最小质因子p,即x会被x / p筛掉,而在i枚举到x之前,一定会先枚举到x / p,并且由于x是合数且最小质因子是p,那么有x / p > p, 所以i枚举到x / p时,质数表里一定含有p,所以x一定会被筛掉,又因为i的枚举范围为2~n,并且2~n的所有合数除以它的最小质因子的范围仍在2~n,所以所有2~n的合数都会被筛掉。

    2)一个合数只会被它的最小质因子筛(已证),且x不会被同一个最小质因子筛多次(x含有多个相同的最小质因子)

    因为合数x是被其最小质因子p筛,即在i = x / p时,将x筛掉,由于x / p是唯一值,所以只需要证明在i = x / p时,只会把x筛一次即可。

    1. 当i = x / p的最小质因子不是p: x只被筛一次

    2. 当i = x / p的最小质因子是p:x只被筛一次

    另外质数是不会被筛掉的,因为质数t的最小质因子为t,而i >= 2, 所以i * t绝对不可能为t,所以t不会被筛掉。

    还有一个注意点:for(int j = 0; primes[j] <= n / i; j ++)为什么不写成for(int j = 0; j < cnt && primes[j] < n / i; j ++),若i为合数则i一定存在质因子,若i为质数,那i一定位于primes数组的最后一个位置,这两种情况都会被if(i % primes[j] == 0) break;停下来,所以j一定会在达到cnt之前停下来。

    总之,线性筛要想清楚:

    1. 为什么合数一定会被筛掉
    2. 为什么质数一定不会被筛掉
    3. 为什么所有合数都只会被筛一次

    复杂度:(O(n))

    给定一个正整数n,请你求出1~n中质数的个数。

    输入格式

    共一行,包含整数n。

    输出格式

    共一行,包含一个整数,表示1~n中质数的个数。

    数据范围

    (1≤n≤10^6)

    #include<iostream>
    using namespace std;
    
    const int N = 1000010;
    
    int st[N], primes[N];
    int cnt;
    int n;
    
    void get_primes(int n){
        for(int i = 2; i <= n; i ++){
            if(!st[i]) primes[cnt ++] = i;
            for(int j = 0; primes[j] <= n / i; j ++){
                st[primes[j] * i] = 1;
                if(i % primes[j] == 0) break;
            }
        }
    }
    
    int main(){
        cin >> n;
        
        get_primes(n);
        
        cout << cnt;
    }
    

    埃氏筛和线性筛耗时在(10^6)时基本相同,在(10^7)时埃氏筛时间翻一倍。

  • 相关阅读:
    mysql启动和关闭外键约束的方法(FOREIGN_KEY_CHECKS)
    如果感觉不好,最后一面你需要更努力表达你的优势。最后一面打动面试官的可能往往是你的真诚和热情
    你的最终目的不是要进入该公司,而是要进入该公司你最想去的部门乃至项目组
    如果你说最近在看《诛仙》,平时喜欢玩LOL,你就是在把自己往悬崖上推
    Qt中嵌入Directx11(有句柄就可以)
    2015腾讯暑期实习武汉站面试经历(大学里的小朋友有这样的技术水平,只能说他规划的好、觉醒的早)
    NET Core开发-获取所有注入(DI)服务
    jQuery.data的是jQuery的数据缓存系统
    理解数字图像处理中的卷积
    Obstack是C标准库里面对内存管理的GNU扩展
  • 原文地址:https://www.cnblogs.com/tomori/p/14245157.html
Copyright © 2011-2022 走看看