素数的简单性质和筛法

素数定理

素数定理(prime number theorem)是素数分布理论的中心定理,是关于素数个数问题的一个命题:设x≥1,以π(x)表示不超过x的素数的个数,当x→∞时,π(x)~Li(x)或π(x)~x/ln(x)。(Li(x)为对数积分 

伯特兰猜想

伯特兰—切比雪夫定理说明:若整数n > 3,则至少存在一个质数p,符合n < p < 2n − 2。另一个稍弱说法是:对于所有大于1的整数n,至少存在一个质数p,符合n < p < 2n。

6n+-1筛法

任何一个自然数,总可以表示成如下形式之一:6N,6N+1,6N+2,6N+3,6N+4,6N+5 (N=0,1,2,3,..),显然,当N≥1时,6N,6N+2,6N+3,6N+4都不是素数,只有形如6N+1和6N+5的自然数才可能是素数,所以除了2,3外,所有的素数都可以表示成6N±1的形式(N=0,1,2,3,..),根据上述分析可以构造一面筛子,只对形如6N±1的自然数进行筛选,来减少筛选的次数。

const int maxn=1000000;
int prime[maxn],nprime=0;
bool Isprime(int x)
{
    if(x%2==0)
        return false;
    for(int i=3;i*i<=x;i+=2)
        if(!(x%i))
            return false;
    return true;
}
void doprime()
{
    prime[nprime++]=2;
    prime[nprime++]=3;
    for(int i=6;i<=maxn;i+=6)
        for(int j=-1;j<=1;j+=2)
            if(Isprime(i+j))  
                prime[nprime++]=i+j;    
}

他的复杂度小于O(sqrt(N)*N),也就是说,这个方法只是一种常数优化算法。如果需要进一步缩小时间复杂度,我们需要更有力的筛法

埃式筛

埃拉托斯特尼筛法,简称埃式筛,是一种由希腊数学家埃拉托斯特尼所提出的一种简单检定素数的算法。要得到自然数n以内的全部素数,必须把不大于根号n的所有素数的倍数剔除,剩下的就是素数

 

这张图就很清晰的展示了这个过程,关于代码实现。

#include<bits/stdc++.h>
using namespace std;
#define MAXNUM 100006
bool is_prime[MAXNUM];
int prime[MAXNUM];
int ini(int n)
{
    int p = 0;
    for(int i = 0 ; i <= n ; i++) is_prime[i] = true;
    is_prime[0]=is_prime[1] = false;
    for(int i = 2 ; i <= n ; i++)
    {
        if(is_prime[i])
        {
            prime[p++] = i;
            for(int j = i ; j <=n ; j+=i) is_prime[j] = false;
        }
    }
    return p;
}
int main()
{
    int k = ini(1000);
    for(int i = 0 ; i < k ; i++) cout << prime[i] << endl;
    cout<<"the prime's number :"<<k<<endl;
    return 0;
}

实际上在使用埃式筛时,如果我们需要找到区间l到r中的素数,那么我们只需要先筛除1到sqrt(r)中的素数,在用这些素数从l筛到r.可以减少很多不必要的运算。

欧拉筛

实际上对于大部分问题,埃式筛已经是一个非常接近线性的筛法了,但是如果我们想追求绝对的线性筛法,那么我们需要进一步优化我们的思路,欧拉筛就是这样做的

欧拉筛法的基本思想 :在埃氏筛法的基础上,让每个合数只被它的最小质因子筛选一次,以达到不重复的目的。

int prime[MAXN];
bool vis[MAXN];
int cnt=0;
void Euler_prime(int n)
{
    for(int i=2;i<=n;++i)
    {
        if(!vis[i])
        {prime[cnt++]=i;vis[i]=true;}//vis[i]置为true或不置true都可以
        for(int j=0;j<cnt;++j)
        {
            if(i*prime[j]>n)//判断是否越界
                break;
            vis[i*prime[j]]=true;//筛数
            if(i%prime[j]==0)//时间复杂度为O(n)的关键!
                break;
        }
    }
}

我们注意到,在用埃式筛法的同时,同一个数字也许会被筛选多次,比如6先被2筛选一次,再被3筛选一次,这样就浪费了很多不必要的时间,而欧拉筛法通过if(i%prime[j]==0)break;这一步就避免了重复筛选的发生,我们举个例子,比如,2先筛选了4,然后进行下一个循环,3筛选6和9,当我们执行到4的时候,可以发现,当i==4时,第一次运行到if(i%prime[j]==0)这一步的时候就直接break;掉了,这也就是说,当我们的合数进入循环时,其实它已经被之前的数筛选过了,所以当合数进入内层循环时,内层循环只执行了一次,从而减少了时间复杂度。

大部分情况下,埃式筛足以解决所有问题,就算不行,欧拉筛也可以解决基本所有问题,但是既然提到了6n+-1这种筛法,我们可以在任意一种筛法的基础上,用这个方法进一步进行常数优化。(虽然基本没用,而且很无聊)

 

posted @ 2020-09-28 01:18  徒手拆机甲  阅读(436)  评论(0)    收藏  举报