首先我们要明确什么是是质数。质数,也可以叫做素数,我们可以理解为给定一个数x,那么 ∀ i ( i ∈ [ 2 , n − 1 ] ) \forall{i} (i\in[2,n-1]) ∀i(i∈[2,n−1]),都存在 x % i ≠ 0 x\% i\neq0 x%i=0,那么,我们就称这个数x为质数。
首先,是最为朴素的算法,很简单,按照定义直接暴力搜索就行了,枚举从2到n-1的所有数字,取余运算完事了,但是,这样筛质数的时间复杂度是 O ( n 2 ) O(n^2) O(n2),很多的情况的,我们要打的表要到 1 e 7 1e^7 1e7,很明显这个方法不适用,但是,还是贴出来代码看一下,因为有的题目数据要求不这么严格,这个方法是最好想的。
但是,即使是最为朴素的方法,优化也是很好想象的,比如,12是个合数(不是质数就是合数,也就是12前面有质因子),2和6是两个质因子,3和4是两个质因子,我们发现,质因子是成双成对出现的,两个质因子肯定是单调不减的,也就是说,我们没有必要搜到n-1,只需要搜到 n \sqrt{n} n就可以了。因为如果搜到 n \sqrt{n} n还没有质因子,那么右半边一定也没有质因子。
那么这样筛选质数的方法就可以优化成 O ( n n ) O(n\sqrt{n}) O(nn)虽然有些问题还是不够。
if(x<=1)
return 0;
for(int i=2;i<=x/i;i++)
{
if(x%i==1)
return 0;
}
return 1;
小tips:
i<=sqrt(x)
,这个不是很好,因为sqrt我个人认为底层可能是用浮点数二分来实现的,这个肯定是对的,但是相对来说速度比较慢i*i<=x
这个也不是很提倡,因为当i比较大的时候,可能会爆int ,所以建议背板子就背正确的。 对于埃氏筛,我们可以先了解一下没有优化的埃氏筛,没有优化的埃氏筛的思想是对于一个数字,他的倍数肯定是合数,比如我们要求10以内的质数,首先进入循环,第一个数是2,他是质数直接加入到答案当中,然后把10以内的2的质数全部打上标记,表示这些数字全是质数。
下面会有一张图 来解释一下这个代码的过程
注意,在代码当中,prime【i】为1代表这个数是合数,否则就为质数,ans数组记录的是所有素数的集合 。
#没有优化的埃氏筛
#include
using namespace std;
const int N=1e8+100;
int prime[N];
int cnt;
int ans[N];
void get_prime(int n)
{
prime[1]=1;
for(int i=2;i<=n;i++)
{
if(prime[i]==0)
{
ans[cnt++]=i;
}
for(int j=i+i;j<=n;j+=i)
{
prime[j]=1;
}
}
}
int main()
{
int n;
cin>>n;
get_prime(n);
cout<
画一下图就如下图所示:
如图所示,黑色表示第一次筛出来的合数被删除了,红色表示从3开始筛选出的合数被删除了,蓝色表明从4开始筛选出的合数被删除。这种欧拉筛的时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)的
但是,我们可以看出来一个问题,比如,我们在2筛选过的数,在4又被筛选了一遍,而这显然不是我们想要的,所以,我们可以优化,就是每次只筛选质数,也就把第二个for循环放到第一个if里面,这样可以把复杂度优化到 O ( n l o g l o g n ) O(nloglogn) O(nloglogn)
代码如下:
#include
using namespace std;
const int N=1e8+100;
int prime[N];
int cnt;
int ans[N];
void get_prime(int n)
{
prime[1]=1;
for(int i=2;i<=n;i++)
{
if(prime[i]==0)
{
ans[cnt++]=i;
for(int j=i+i;j<=n;j+=i)
{
prime[j]=1;
}
}
}
}
int main()
{
int n;
cin>>n;
get_prime(n);
cout<
但是,即使是.埃氏筛,在一些问题当中依旧会tle,所以,我们就要使用 线性筛,线性筛的时间复杂度是 O ( n ) O(n) O(n)的,相对来说比较优秀 .
代码如下
#include
using namespace std;
int n;
const int N=1e8+100;
int ans[N],prime[N];
int cnt;
void get_prime(int n)
{
prime[1]=1;
for(int i=2;i<=n;i++)
{
if(prime[i]==0)
{
ans[cnt++]=i;
}
for(int j=0;ans[j]<=n/i;j++)
{
prime[ans[j]*i]=1;
if(i%ans[j]==0)
{
break;
}
}
}
}
int main()
{
cin>>n;
get_prime(n);
cout<
其实,线性筛的优化我们也可以管中窥豹在前面看到,优化之后的欧拉筛可以优化掉2和4对8的重复筛选,却不可以优化掉2和3对于6的重复筛选,而我们的线性筛选就是用最小质因子来筛选出质数。因为,每个数的最小质因子只有一个,所以我们每个数只会被筛选一次。
这个算法的核心就是每个数字都只会被他的最小质因子筛选出来 那么这是为什么那 。
我们首先要知道一个前提 那就是 对于任意一个数 n n n都有
n = p 1 α 1 ∗ p 2 α 2 ∗ … ⋯ ∗ p x α x ( 1 ) n=p_1^{\alpha_1}*p_2^{\alpha_2}*\ldots\dots*p_x^{\alpha_x} (1) n=p1α1∗p2α2∗…⋯∗pxαx(1)
其中 p 1 … p k p_1\ldots p_k p1…pk为这个数的质因子
然后我们再去分情况讨论 先来证明 ans[j]*i当中的ans[j]一定是他们乘积的最小质因子
首先就是当i%ans[j]==0的时候 这个时候 我们 首先可以确定 ans[i]是 i的最小质因子 因为我们的 j是从小到大遍历的,也就是ans[j]是单调递增的
那么 ans[j]就是i的多项式展开当中的 p 1 p1 p1 那么 primes[j]*i多项式展开中的 p 1 p1 p1也一定是ans[j] 。我们可以这么理解 既然ans[j]式i多项式展开当中的 p 1 p1 p1 那么 他们两个相乘只不过是在i的多项式当中 p 1 p1 p1的指数加一
当i%ans[j]==0 的时候得证
第二种情况就是 i%ans[j]!=0的时候 这种情况我们可以这么理解 等于0的时候就是i的最小质因子 ,那么没找到之前 一定比i的最小质因子 p 1 p1 p1要小(还是那个原因 j是从小往大遍历) 那么他们两个相乘不过是在i的多项式展开当中加入了一项比 p 1 p1 p1还小的质因子成为了新的 p 1 p1 p1 那么相乘之后 我们的ans[j]也肯定是 最小质因子
综上 ans[j]一定是最小质因子得证
然后就是说一下为什么能全筛选出来 原因其实很简单 就是每个数一定都要最小质因子 这个最小质因子是他本身就是质数 其他的 我们都能筛选出来
这个是比较直观的理解 我们可以假设给定一个合数n 他一定是存在不等于他的最小质因子 p j p_j pj 当我们在枚举到 n之前 一定枚举过n/ p j pj pj 也就是说n已经被筛过
最后就是说一下细节问题 就是代码当中 for(int j=0;ans[j]<=n/i;j++) 这个语句j