【数论-1】质数/素数 筛法

质数/素数的筛法

1.质数/素数的朴素筛法

bool isprime[MAXN];
for(int i=2;i<=n;i++){
    isprime[i]=1;
    for(int j=2;j*j<=i;j++){
        if(i%j==0){
            isprime[i]=0;
            break;
        }
    }
}

关于此种筛法,对于一些简单的入门题目是可以通过的(P1125

但不足之处是很明显的,对于算法竞赛,“TLE”也是不能通过题目的一项重要原因。

那么如何优化呢?枚举的次数过多时,可以减少枚举的次数。

优化:枚举到sqrt(n)即可

bool isprime[MAXN];
int prime[MAXN],cntprime=0;
for(int i=3;i<=n;i++){
    isprime[i]=1;
    for(int j=1;prime[j]*prime[j]<=i&&j<=cntprime;j++){
        if(i%prime[j]==0){
            isprime[i]=0;
            break;
        }
    }
    if(isprime[i]==1){
        prime[++cntprime]=i;
    }
}

2.质数/素数的埃氏筛+欧式筛

对于一个质数,它的倍数一定不是质数(可通过质数的定义来证明)

那么我们可以筛出一个质数,然后对它的倍数进行标记:

bool isntprime[MAXN]={0};
for(int i=2;i<=n;i++){
    if(isntprime[i]==1) continue;
    for(int j=i+i;i<=n;j+=i){
        isntprime[j]=1;
    }
}

复杂度为O(nloglogn)

但其也不是完美的解决方案,比如12=2*6,12=3*4 这样的数字可能会被筛多次(>=2)

这样就造成了时间的浪费

我们对其改进:

bool isntprime[MAXN]={0};
for(int i=2;i<=n;i++){
    if(isntprime[i]==1) continue;
    for(int j=i*i;i<=n;j+=i){
        isntprime[j]=1;
    }
}

从p^2开始筛除即可,可将其优化,但其时间复杂度不改变

欧式筛(欧拉筛法)

欧拉(Euler)筛法,简称欧式筛,或因为其线性复杂度被称呼为线性筛。由瑞士数学家欧拉提出

它在埃氏筛的基础上,用一个方法,限定了每个数只被其最小质因数 fcfc 筛到一次,从而保证时间复杂度为 O(n)O(n)

思想比较巧妙:

对于当前数字 nn ,假设它的最小质因数为 fcfc

对于已经筛出的质数,存在表 prime 中

那么,我们从质数表中,枚举最小质因数不大于 fcfc 的质数 pp

我们就能保证: p×np×n 的最小质因数一定为 pp

那么,事先没被标记最小质因数的数字就一定是质数,且最小质因数为它本身

代码实现如下:

vector<int> Prime;
int fc[MAXN];
for(int i=2;i<=n;i++){
    if(fc[i]==0){
        fc[i]=i;
        Prime.push_back(i);
    }
    for(auto p : Prime)
        if(p>fc[i]||p*i>n) break;
        else fc[p*i]=p;
}//STL版本 

时间复杂度O(n)

3.对欧拉筛法空间的优化

对于欧式筛法,已经足够优化速度,但对于空间的优化几乎为0

如果你开一个prime[1e8+1],flag[1e8+1](int类型)

那么不难想象空间会如何超出题目限制(P3383 P3912

如果你使用Int型数组 则难逃一死

so,可以开一个Bool 数组来储存每个数字的状态(被筛与否)

然后处理

#include <iostream>
using namespace std;
const int maxn=1e8+1;
int n=0;int q=0;
int k=0;
int cnt=0;
bool flag[maxn]={0};
int primes[maxn]={0};
void getprimes(int x){
    for(int i=2;i<=x;i++){
        if(!flag[i]){
            primes[++cnt]=i;
        }
        for(int j=1;j<=cnt;j++){
            if(i*primes[j]>x){
                break;
            }
            flag[i*primes[j]]=1;
            if(!(i%primes[j])){
                break;
            }
        }
    }
}
int main(){
    std::ios::sync_with_stdio(0);
    cin>>n>>q;
    getprimes(n);
    for(int i=1;i<=q;i++){
        cin>>k;
        cout<<primes[k]<<endl;
    }
    return 0;
}
P3383
#include <iostream>
using namespace std;
const int max1=1e8+1;
bool flag[max1]={0};//存储标记
int primes[max1];//存素数
int ans=0;
void getprimes(int x){
    for(int i=2;i<=x;i++){
        //从2开始判断质数,1不是质数 
        if(!flag[i]){
            //默认0,取反之后为true->执行计数和存储到数组中
            
            primes[++ans]=i; 
            
        }
        for(int j=1;j<=ans;j++){//搜索小于prime[i]的所有质数 
            if(i*primes[j]>x){
                break;
                //1.i*prime[j]不能大于所求范围n;    
            }
            flag[i*primes[j]]=1;//标记flag[质数*质数]=合数
            if(!(i%primes[j])){
                break;//2.如果prime[j]是i的一个质因子,则退出,作为优化(后续的i*prime[j]会有更小的质因子存在,为了不除多次) 
            } 
        } 
    }
}
int main(){
    int n;
    cin>>n;
    getprimes(n);
    cout<<ans;
    return 0;
    
} 
P3912

还可以使用<bitset>的奇怪方法(Orz大佬)

 

posted @ 2021-10-27 19:17  Niruri  阅读(322)  评论(1)    收藏  举报