素数筛法一直是我前期学习的难题,现在把它总结一下,防止忘记。
① 普通筛法 O(n√n)
根据定义,一个合数n一定可以由两个数相乘得到,这两个因数一个大于√n,另一个小于√n,所以可以对因数从2到√n进行枚举,判断是否可以被n整除,如无法整除,则为素数。
② 埃氏筛法 O(n㏒n)
如果一个数是素数,那么他的倍数就一定是合数,就可以在给定范围内将他的倍数先筛掉,所以我们可以开一个标记数组,先假定开始所有数的状态都是素数(即未被标记)。首先我们从2到n开始枚举,如未被标记,则该数为素数,我们再将该数在n内的所有倍数筛掉(标记)。
#include<bits/stdc++.h>
using namespace std;
int prime[50005],//储存素数
flag[50005],//标记数组
cot;//计数
int main()
{
int n;
cin>>n;
for(int i=2;i<=n;i++)
{
if(!flag[i])//如未被标记,即是素数
{
prime[cot++]=i;//加入素数数组中
for(int j=2*i;j<=n;j+=i)//将倍数都标记
{
flag[j]=1;
}
}
}
// printf("%d
",cot);
// for(int i=0;i<cot;i++)
// {
// printf("%d ",prime[i]);
// }
return 0;
}
③线性筛法(欧拉筛) O(n)
因为在埃氏筛法中,有的数被筛了多次,如6,都被2和3筛过,所以要进行优化,保证每个数只被筛了一次。在欧拉筛法中,我们对每个数都进行枚举,将它与已得到的素数的乘积筛去,而为了保证每个数不重复筛去,如果该数可以被已得到素数整除,那么说明已被筛过。
#include<bits/stdc++.h>
using namespace std;
int prime[50005],//存素数
flag[50005],//标记数组,是否被筛
cot;
int main()
{
int n;
cin>>n;
for(int i=2;i<=n;i++)
{
if(!flag[i])//如未被筛
{
prime[cot++]=i;//储存
}
for(int j=0;j<cot&&prime[j]*i<=n;j++)
{
flag[prime[j]*i]=1;//将倍数筛去
if(i%prime[j]==0)//i这个数已被筛,跳出
break;
}
}
// printf("%d
",cot);
// for(int i=0;i<cot;i++)
// {
// printf("%d ",prime[i]);
// }
return 0;
}
其实在埃氏筛中,我们最外层循环是找到素数,然后从再将其所有倍数筛去;
而在欧拉筛中,我们的最外层循环其实就枚举了倍数,然后再在内层循环中与已得到素数相乘,筛去更大的倍数(其实这里就与埃氏筛类似,将素数的倍数筛去),而 if(i%prime[j]==0) break; 这句确保了每个数只被筛一次,以此降低了时间复杂度。