求素数(从判断素数到筛法)

判断素数

  • 最简单的判断就是根据素数的定义:只有两个因子1和本身(1不是素数)。时间复杂度O(n)
bool is_prime(int x){
    if(x == 1) return false;
    rep(i , 2 , n-1){
        if(x % i == 0){
            return false;
        }
    }
    return true;
}
  • 我们知道因子都是成对出现的,所以在枚举时,可以只枚举2-\(\sqrt x\) .时间复杂度O(\(\sqrt n\))
bool is_prime(int x){
    if(x == 1) return false;
    rep(i , 2 , sqrt(x)){
        if(x % i == 0){
            return false;
        }
    }
    return true;
}
  • 数学上有个定理,只有形如6n-1,6n+1的自然数才可能是素数。简要说明一下:所有自然数都可以写成6n,6n+1,
    6n+2,6n+3,6n+4,6n+5.可以简单的根据偶数一定不是素数排除6n,6n+2,6n+4.而6n+3可以写成3(2n+1)是大于3的倍数也一定素数。所以可以进一步优化。
bool is_prime(int x){
    if(x == 2 || x == 3) return true;
    if(x == 1 || (x%6!=1 &&x%6!=5)) return false;
    rep(i , 2 , sqrt(x)){
        if(x % i == 0){
            return false;
        }
    }
    return true;
}

埃氏筛法和欧拉筛法

  • 埃氏筛法思路:用已经筛选出来的素数去过滤所有能被它整除的数,即满足p|x的自然数x。我们知道2是最小的素数,通过2筛去所有2的倍数。然后往后
    3没有被筛,所以3是素数,通过3筛去所有3的倍数,以此类推。时间复杂度(\(nlog(log(n))\)),虽然欧拉筛时间复杂度降到O(n),但埃氏筛法时间复杂度已经非常优秀,足够使用,并且埃氏筛法实现简单
    时间复杂度分析:小于\(\frac{n}{2}+\frac{n}{3}+...+\frac{n}{n} = nlogn\).时间复杂度为O(nlog(log(n))).
void Erasieve(int n){//筛选1-n间的素数
    rep(i , 1 , n) is_prime[i] = true ;//初始都为素数
    is_prime[1] = false; is_prime[2] = true ;
    rep(i , 2 , n){
        if(is_prime[i]){//判断是否为素数
            //prime[++len] = i ;//加入素数表
            for(int j = i*i ; j <= n ; j+=i){//通过该素数筛去素数倍数,j初始为i*i,而不是2*i,是因为我们知道2的倍数已经被筛了,算是一个小小的优化
                is_prime[j] = 0;
            }
        }
    }
}
  • 欧拉筛法:因为埃氏筛法可能对一个数进行多次筛除,比如30,会被2和5各筛除1次。而欧拉筛法是对埃氏筛法的优化
    使每一个数被筛除一次。要使每个数被筛除一次,则需要知道唯一分解定理。使每一个合数只被其最小质因数筛去。
    具体思路:对于任意一个自然数n,假设n的最小质因数为m,那么我们用所有小于m的质数\(p_i\)乘上n可以得到一个合数。对于这个合数而言,
    \(p_i\)一定是该合数最小质因数。因为n的最小质因数为m,\(p_i\)又为小于m的质数(可以看成是两部分相乘,一部分是大于m,这部分可能是质数也可能是合数,另一部分是\(p_i\)),
    所以\(p_i\)一定是该合数的最小质因数。
void oulashai(int n){//筛选1-n素数
    ME(is_prime , true);
    is_prime[1] = false;
    for(int i = 2 ; i <= n ; i++){
        if(is_prime[i]){
            prime[++len] = i ;//素数表
        }
        for(int j = 1 ; j <= len && prime[j] * i <= n ; j++){//素数表保证p为素数
            is_prime[i*prime[j]] = false;//筛除n*p
            if(i % prime[j] == 0) break;//保证p是小于等于m的最小质因数
        }
    }
}

埃氏筛法

应用一:区间无平方因子

求[n,m]区间无平方因子数的个数。整数p无平方因子,当且仅当不存在k>1,使得p是\(k^2\)的倍数.\((1<=n<=m<=10^{12} , m-n<=10^7)\)
思路:首先筛选出不超过\(\sqrt{m}\)素数表,然后对素数的平方进行筛选。核心思想是埃氏筛法。

int prime[maxn] , len;//素数表
bool is_prime[maxn];//标记素数
bool dprime[maxn];//标记具有平方因子数的数
void solve(){
    int n , m , cnt = 0;
    cin >> n >> m;//[n,m]区间
    ME(is_prime , true);//初始化都为素数
    is_prime[1] = 0 ;//1不是素数
    for(int i = 2 ; i * i <= m ; i++){
        if(is_prime[i]){//通过素数筛掉素数倍数,剩下的就是素数
            prime[++len] = i ;
            for(int j = i * i ; j <= m ; j += i){//2*i已被素数2筛去。所有从i*i开始
                is_prime[j] = 0 ;
            }
        }
    }
    for(int i = 1 ; i <= len ; i++){
        int p = prime[i]*prime[i];
        for(int j = p ; j <= m ; j += p){//类似埃氏筛法,筛掉含素数平方因子的数
            dprime[j] = 1 ;
        }
    }
    rep(i , n , m){
        if(!dprime[i]){//剩下的就是无平方因子的数
            //cout << i << endl;
            cnt++;
        }
    }
    cout << cnt << endl;
}

应用二:区间筛法

给定整数l,r,求区间[l,r]的素数个数。\(l<=r<=10^{12} , r-l<=10^6\)
解法:筛出\(1-10^6\)质数 , 因为小于\(10^{12}\)的合数的最小质因子不超过\(10^6\)。可跳跃式的筛去较大的数。

for(int i = 2 ; i <= 1000000 ; i++){
    if(!pr[i]){
        for(int j = i * i ; j <= 1000000 ; j+=i){
            pr[j] = 1 ;
            if(j >= l && j <= r){
                vis[j-l] = 1 ;
            }
        }
        for(int j = max((ll)2 , (l+i-1)/i) ; j * i <= r ; j++){
            vis[j*i-l] = 1 ;
        }
    }
}
if(l == 1){
      vis[0] = 1 ;
}
vector<int>v;
for(int i = 0 ; i <= r-l ; i++){
    if(!vis[i]){
        v.push_back(i+l);
    }
}

应用三:统计1-n每个数质因子个数

运用了埃氏筛法的思想.

void solve(int x){
    pr[1] = 0 ;
    for(int i = 2 ; i <= x ; i++){
        if(!pr[i])
        for(int j = i ; j <= x ; j *= i){
            for(int k = j ; k <= x ; k += j){
                pr[k]++;
            }
        }
    }
}
posted @ 2020-06-04 18:37  无名菜鸟1  阅读(459)  评论(0编辑  收藏  举报