素数筛是一种将 2-n 范围内的素数快速筛选出来的方法,本篇文章将讲到两种筛法,埃氏筛和欧拉筛。
介绍
素数(也叫质数)是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数。最为朴素的判断一个数是不是素数的方法,就是遍历每一个比它小的且大于1的数,看能否被这个数整除。如果遍历结束都没有找到这样的数,那么判断这个数是一个素数。
基于这种方法,如果我们要寻找一个范围内的素数,这个做法需要对每一个数进行逐一判断,总的复杂度就会很高。即使我们能将判断单个素数时的循环次数降低到sqrt(n),这个暴力算法还是十分差劲。
埃氏筛
埃氏筛就是把素数的倍数给筛掉,会出现重复筛的情况,比如6会被2和3筛,30会被2,3,5筛
bool isPrime[10000]={0};//用来标记一个数是不是素数 ,0表示是素数,1表示不是素数
void AiPrime (int n)
{
isPrime[0]=isPrime[1]=1;//0和1都不是素数
for(int i=2;i<=n;i++){
if(!isPrime[i]){//如果这个数是素数
for(int j=i;i*j<=n;j++){//循环条件保证不会超
isPrime[i*j]=1;//把素数的倍数筛掉
}
}
}
}
一般情况下,埃氏筛够用了,其时间复杂度是O(nlognlogn)
欧拉筛
欧拉筛就是为了解决埃氏筛被重复筛的情况,说一个结论:每一个合数都有一个最小的质因子。所以,我们要保证每个合数只会被他的最小质因子筛掉,这样就可以避免很多重复的操作。为此,我们要维护一个数组,这个数组用来存取已发现的素数。
#include<iostream>
using namespace std;
bool isPrime[10000]={0};//用来标记一个数是不是素数 ,0表示是素数,1表示不是素数
int num = 0;//用来统计素数的个数
int Prime[10000];//存素数
//欧拉筛
void EulerPrime(int n)
{
isPrime[0]=isPrime[1]=1;
for(int i=2;i<=n;i++){
if(!isPrime[i])Prime[num++]=i; //如果这个数是素数,就存起来
for(int j=0;j<num;j++){
if(i*Prime[j]>n)break;//范围超了就跳出
isPrime[i*Prime[j]]=1;//标记合数
if(i%Prime[j]==0)break;//保证用最小质因子筛,避免重复标记
}
}
}
来做个题:
思路:就是把素数筛换成漂亮数筛,只需要增加一行代码,由于要存区间内的漂亮数多少,我们再维护一个数组,表示第n个数前面有多少个漂亮数。
参考代码
#include<iostream>
using namespace std;
const int N=1e8+10;
int a[N];
int num=0;
bool vis[N]={false};//记录当前数是不是漂亮数
bool b[N]={false};//标记是不是素数
int ans[N]={0};//N之前有多少个漂亮数
void EulerPrime(int n)//线性筛选出漂亮数
{
b[0]=true;b[1]=true;
for(int i=2;i<=n;i++){
if(b[i]==false){
a[num++]=i;
}
for(int j=0;j<num;++j){
if(a[j]*i>n)break;
b[a[j]*i]=true;
/*就是多了下面这一行代码,
如果i也是素数就把两个相乘的数标记成漂亮数 */
if(!b[i])vis[a[j]*i]=true;
if(i%a[j]==0)break;
}
}
for(int i=1;i<=n;i++){//标记i之前有多少个漂亮数,可以理解成前缀和
ans[i]+=ans[i-1]+(vis[i]==1);
}
}
int main()
{
int t;
cin>>t;
int l,r;
EulerPrime(1e8+10);
while(t--){
cin>>l>>r;
cout<<ans[r]-ans[l-1]<<endl;
}
}
OVER!