[算法系列]数论02-素数筛法

395 阅读1分钟

素数筛法

上一回,我们介绍了素数的判定法,这一次,我们主要来介绍一下素数筛法。所谓素数筛法,就是在一个很大范围的数中,筛选出素数

埃氏筛法

埃氏筛法是素数筛法中非常简单的一种筛法,但是由于其复杂度相对于另一种欧拉筛法来说比较高,我们用的不多

算法原理:

我们知道对于一个数而言,其倍数一定不是素数,埃氏筛法正是利用这一点,从小到大枚举每一个数字,将自己的倍数标记为合数,到最后,没有被标记的就是素数

时间复杂度分析:

埃氏筛法的时间复杂度分析很有意思,我们来讨论一下:

image.png

上面的分析事实上并不严谨且不是重点,如果数学知识有所欠缺的读者可以不去理会,我们重点看一下代码实现

代码实现:

int v[MAXN];
void prime(int n){
    memset(v,0,sizeof v);
    for(int i=2;i<=n;i++){
        if(v[i])continue;
        for(int j=i;j<=n/i;j++){
            v[i*j]=1;
        }
    }
}

欧拉筛法

欧拉筛法又叫线性筛法,是我们最为常用的素数筛法,相比于埃氏筛法,它的时间复杂度要更低

算法原理:

埃氏筛法中,有些步骤是重复做了的,一个数x的因子有很多,我们使用埃氏筛法,遍历到每个因子的时候都会筛一遍x,这无疑增加了不必要的时间成本,欧拉筛法中,我们保证每个应该被筛的数只会被它的最小质因数筛掉一次,欧拉筛的时间复杂度是: O(n),我们在这里不做证明,读者可以自行分析并计算之。在欧拉筛中,我们每遍历到一个2到n之间的数,如果其没被筛掉,我们就把它存进素数数组,并且我们遍历素数数组中的所有素数,此时,由于单调性,所有i*primes[j]的数的最小质因数一定是primes[j],特殊情况就是i自己就满足其是primes[j]的倍数,我们直接break即可,大家结合下面的代码看

代码实现:

const int N=1e5+10;
int n,primes[N],cnt;
bool vis[N];
void euler(){
    for(int i=2;i<=n;i++){
        if(!vis[i]) primes[++cnt]=i;
        for(int j=1;j<=cnt&&i*primes[j]<=n;j++){
            vis[i*primes[j]]=1;
            if(i%primes[j]==0){
                break;
            }
        }
    }
}

光看不练假把式,下面的练习题各位可以做一下!

练习题

质数距离

题目给定一个闭区间[L,R],你需要找出区间内距离最近的两个质数以及最远的两个质数

数据范围

1<=L<R<=2^31-1

输入样例

2 17
14 17

输出样例

2,3 are closest, 7,11 are most distant.
There are no adjacent primes.

--选自《算法竞赛进阶指南》或者ACwing 196题

AC Code:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=1000010;
typedef long long ll;
int primes[N],cnt;
bool vis[N];
void euler(int n){
    memset(vis, 0, sizeof vis);
    cnt = 0;
    for (int i = 2; i <= n; i ++ )
    {
        if (!vis[i]) primes[cnt ++ ] = i;
        for (int j = 0; primes[j] * i <= n; j ++ )
        {
            vis[i * primes[j]] = true;
            if (i % primes[j] == 0) break;
        }
    }
}
int main(){
    int l,r;
    while(cin>>l>>r){
        euler(50000);
        memset(vis,0,sizeof vis);
        for(int i=0;i<cnt;i++){
            ll p=primes[i];
            for(ll j=max((l+p-1)/p*p,2*p);j<=r;j+=p){
                vis[j-l]=1;
            }
        }
        cnt=0;
        for(int i=0;i<=r-l;i++){
            if(!vis[i]&&i+l>=2){
                primes[cnt++]=i+l;
            }
        }
        if(cnt<2){
            cout<<"There are no adjacent primes."<<endl;
        }
        else{
            int minp=0,maxp=0;
            for(int i=0;i+1<cnt;i++){
                int d=primes[i+1]-primes[i];
                if(d<primes[minp+1]-primes[minp]) minp=i;
                if(d>primes[maxp+1]-primes[maxp]) maxp=i;
            }
            printf("%d,%d are closest, %d,%d are most   distant.\n",primes[minp],primes[minp+1],primes[maxp],primes[maxp+1]);
        }
    }
    
    
    return 0;
}

点评: 这题的核心思路是一个数x如果是合数,一定存在小于等于sqrt(x)的质因子,我们可以据此筛掉区间内质因子倍数的数,剩下的就是质数了,再进行计算即可