以洛谷P3383作为例题
主要目的就是以一个比较快的速度筛出1~n中的素数,并按照顺序保存在一个数组中,从而实现O(1)的查询操作
先说说埃氏筛吧
这个的思路很简单:
先找到一个素数,然后将它的2倍,3倍,4倍……直至n倍(也就是直至这个素数的某个倍数已经超过了你所要筛选的区间)标记为不是素数
然后继续遍历下一个数,若该数为素数,再次执行上述操作;若为合数,则继续遍历下一个
直至将1~n的数都遍历完成
上代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
int prime[100000000],n,m,a,tot;
bool check[100000000];
inline int read(){
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-') w=-1;ch=getchar();}
while(ch>='0'&& ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;
}
int main()
{
n=read();m=read();
memset(check,1,sizeof(check));//1代表该数为质数
check[1]=0;
for(int i=2;i<=sqrt(n);i++){//仔细体会为啥是sqrt(n)
if(check[i]==1)
for(int j=2;j*i<n;j++)
check[i*j]=0;
}
for(int i=1;i<=n;++i){
if(check[i]==1) prime[++tot]=i;
}
for(int i=1;i<=m;i++){
a=read();
printf("%d\n",prime[a]);
}
return 0;
}
欧拉筛:
我们再想一想埃氏筛,会不会出现一个数会被多次筛中?
2*6=12 3*4=12
可以发现,对于12,在整个筛选过程中,它会被筛两次,对于更大的数字,可能会被多筛几十次,所有有没有办法让某个合数,有且仅有一次被筛掉的机会,来提高筛选速度呢?
可以用“最小质因数 × 最大因数(非自己) = 这个合数”的途径删掉。由于每个数只被筛一次,所以时间复杂度为 O(n)
其中的关键就是 最小质因数 × 最大因数(非自己) = 这个合数
即我们要保证,通过某个操作筛掉的合数,一定是被它自己的最小质因数筛掉的
先上代码
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
bool check[100000000];
int prime[100000000],n,m,a,tot;
inline int read(){
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-') w=-1;ch=getchar();}
while(ch>='0'&& ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;
}
int main()
{
n=read();m=read();
memset(check,1,sizeof(check));//1代表该数为质数
check[1]=0;
for(int i=2;i<=n;++i)
{
if(check[i]==1) prime[++tot]=i;
for(int j=1;j<=tot and i*prime[j]<=n;j++){
check[i*prime[j]]=0;
if(i%prime[j]==0) break;
}
}
for(int i=1;i<=m;i++){
a=read();
printf("%d\n",prime[a]);
}
return 0;
}
我把关键的步骤抽出来
for(int i=2;i<=n;++i)
{
if(check[i]==1) prime[++tot]=i;
for(int j=1;j<=tot and i*prime[j]<=n;j++){
check[i*prime[j]]=0;
if(i%prime[j]==0) break;
}
}
这里的i,是在枚举每个数,也是在枚举每个倍数(与埃氏筛不一样!埃氏筛的j才是在枚举倍数)
check[i*prime[j]]=0;
这句是在筛合数,注意,我们需要保证每个合数都要被它最小的质因数筛掉(就是要保证prime[j]是i*prime[j]的最小质因数,3*4=12,但3不是12的最小质因数)
所以有了下面这句
if(i%prime[j]==0) break;
假设i为prime[j]的某个倍数后,再执行check[i*prime[j的下一个]]=0; 因为在数组prime中素数是由小到大排列的,再次执行的话就相当于
check[prime[j]*某个倍数*一个比prime[j]大一点的素数]=0
这样筛的合数就不是被自己最小的那个prime筛掉的了
比如 4*3=12 就相当于是 素数2*倍数2*素数3=12 而12本应该是 素数2*倍数6=12
OK
结束!!!
QQ:1206668472