『数论浅谈:素数』


<更新提示>

<第一次更新>


<正文>

素数(prime)

定义

如果大于1的正整数p仅有的正因子是1和p, 则称p为素数。

与之对应,在自然数中,不是质数的就是合数,其定义如下:
合数(compound):大于1又不是素数的正整数称为合数。

本篇博客我们探讨关于素数在oi中的一些问题。

先注意到一句关于合数的说明:
如果n是合数, 则n必有一个小于或等于n1/2的素因子。这句话极易用反证法证明,这边不再赘述。但在后面的应用中,它将为我们算法的时间复杂度做出巨大的贡献。

素数的判断

求解1~n所有的素数。
想想,我们怎样判断一个数x是不是素数?
显然,最暴力的方法就是枚举2~x-1之间的所有数,判断是否有一个数i能整除x,如果存在,则x不为素数。那么这种算法的时间复杂度为O(N)。显然,在求解1~n所以的素数时会超时,上文中已经提到:如果n是合数, 则n必有一个小于或等于n1/2的素因子,那么我们其实枚举1~x1/2的数就可以判断了,其代码如下:

inline bool check(k)
{
    for(int i=2;i*i<=k;i++)
    if(k%i==0)return 0;
    return 1;
}

那么,一一判断1~n中的每个数是不是素数即可,时间复杂度上限为O(n·n1/2),即O(n3/2)。
在某些时候,O(n3/2)的时间复杂度显然是不够的,这个算法主要用于判断素数,但在1~n所以素数的求解中,我们需要更快的算法。

埃式筛法

对于求1~n所有的素数,我们考虑如下方法:我们由算术基本定理得知,每个合数必然由若干个素因子的若干次方相乘得到,所以我们可以通过已经找到的素数,并不断排除他们的倍数,对他们标记为不是素数,就能在O(nlog2n)的时间内得到1~n直接的1所以素数。

e.g.

2是素数, 删除2*2, 2*3, 2*4, …, 2*50
第一个没被删除的是3, 删除3*3, 3*4,3*5,…,3*33
第一个没被删除的是5, 删除5*5, 5*6, … 5*20
得到素数p时, 需要删除p*p, p*(p+1), …p*[n/p], 运算量为[n/p]-p, 其中p不超过n1/2

代码实现如下:

#include<bits/stdc++.h>
using namespace std;
bool isPrime[100080]={};
int n,Prime[100080]={},cnt=0;
void Search()
{
    memset(isPrime,true,sizeof(isPrime));
    isPrime[0]=false;isPrime[1]=false;
    for(int i=2;i<=n;i++)
    {
        if(isPrime[i]) Prime[++cnt]=i;
        for(int j=1;j<=cnt&&i*Prime[j]<=n;j++)
        {
            isPrime[i*Prime[j]]=0;
            if(!(i%Prime[j])) break;
        }
    }
}
int main()
{
    cin>>n;
    Search();
    for(int i=1;i<=n;i++)if(isPrime[i])cout<<i<<' '; 
    cout<<endl;
    return 0;
}
欧拉筛法

在模拟埃式筛法的过程中,不难发现有些数字被“筛”了好多次,造成了时间复杂度的浪费。我们若考虑一种时间复杂度为线性的筛法,我们尝试解决重复问题。

线性筛法的核心原理就是一句话: 每个合数必有一个最大因子(不包括它本身,用这个因子把合数筛掉。)还有另一种说法(每个合数必有一个最小素因子,用这个因子筛掉合数)

e.g.

2:筛掉4
3:筛掉6,9
4:筛掉8
5:筛掉10,15,25
6:筛掉12
7:筛掉14,21,35,49
8:筛掉16
9:筛掉18,27
10:筛掉20
11:筛掉22
12: 筛掉24
13: 筛掉26
14: 筛掉28
15: 筛掉30

我们通过不断筛选得到的素数表来相乘,再次筛选,停止筛选有两种情况:
1 素数表中的数已经使用完毕,继续筛选必定是重复操作,停止。
2 素数表中的素数整除了当前的数,也就是说,如果继续下筛,那么这个筛的数字必然存在更小的素因子,就必然存在未来的某个更大的因子,是一定会在未来被筛去的,所以停止,现在不用筛。
(注意这句话:每个合数必有一个最大因子(不包括它本身,用这个因子把合数筛掉。)还有另一种说法(每个合数必有一个最小素因子,用这个因子筛掉合数))

这样,我们就能做到线性筛了,时间复杂度O(N),代码实现如下:

#include<bits/stdc++.h>
using namespace std;
bool isPrime[100080]={};
int n,Prime[100080]={},cnt=0;
void Search()
{
    memset(isPrime,true,sizeof(isPrime));
    isPrime[0]=false;isPrime[1]=false;
    for(int i=2;i<=n;i++)
    {
        if(isPrime[i]) Prime[++cnt]=i;
        for(int j=1;j<=cnt&&i*Prime[j]<=n;j++)
        {
            isPrime[i*Prime[j]]=0;
            if(!(i%Prime[j])) break;
        }
    }
}
int main()
{
    cin>>n;
    Search();
    for(int i=1;i<=n;i++)if(isPrime[i])cout<<i<<' '; 
    cout<<endl;
    return 0;
}

<后记>


<废话>

猜你喜欢

转载自blog.csdn.net/Prasnip_/article/details/80952178