素数筛

筛选出从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)时埃氏筛时间翻一倍。

原文地址:https://www.cnblogs.com/tomori/p/14245157.html