本篇博客介绍三种素数的求法,即朴素求法、埃氏筛法和欧拉筛(线性筛)。
(1)朴素求法
朴素求法的思想是:若 n 能整除 1 和 n 之外的数,则说明 n 是合数,否则 n 是素数。
1 bool judge(int n){ 2 if(n==1) return false; 3 else{ 4 for(int i=2;i*i<=n;i++){ 5 if(n%i==0){ 6 return false; 7 } 8 } 9 return true; 10 } 11 }
(2)埃氏筛法
埃式筛法的思想是:对于不超过 n 的每个非负整数 p,删除 2*p, 3*p, 4*p,...,当处理完所有数之后,还没有被删除的就是素数。如果用 vis[i] 表示 i 已经被删除,筛法的代码可以写成:
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int maxn=10000000; 4 bool vis[maxn+5]; //0代表素数,1代表非素数 5 void Init(int n){ 6 memset(vis,0,sizeof(vis)); 7 vis[1]=1; //1既不是素数也不是合数 8 for(int i=2;i<=n;i++){ 9 for(int j=i*2;j<=n;j+=i){ 10 vis[j]=1; 11 } 12 } 13 } 14 int main(){ 15 Init(maxn); 16 return 0; 17 }
下面来改进这份代码。首先,在 “ 对于不超过 n 的每个非负整数 p ” 中,p 可以限定为素数——只需在第二重循环前加一个判断 if(!vis[i]) 即可。另外内层循环也不必从 i*2 开始,因为它已经在 i=2 时被筛掉了。然后,将外层循环的下界改为 m ,因为大于 m 的素数的倍数已经被筛过了。改进后的代码如下:
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int maxn=10000000; 4 bool vis[maxn+5]; //0代表素数,1代表非素数 5 void Init(int n){ 6 int m=sqrt(n+0.5); 7 memset(vis,0,sizeof(vis)); 8 vis[1]=1; //1既不是素数也不是合数 9 for(int i=2;i<=m;i++){ 10 if(!vis[i]){ 11 for(int j=i*i;j<=n;j+=i){ 12 vis[j]=1; 13 } 14 } 15 } 16 } 17 int main(){ 18 Init(maxn); 19 return 0; 20 }
(上述埃氏筛法的内容摘自算法竞赛宝典入门经典(第二版))
(3)欧拉筛(线性筛)
欧拉筛的思想是:在埃氏筛法的基础上,让每个合数只被它的最小质因子筛选一次,以达到不重复的目的。
代码如下:
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int maxn=10000000; 4 bool vis[maxn+5]; //0代表素数,1代表非素数 5 int prime[maxn+5]; //prime[i]表示第i个素数 6 void Init(int n){ 7 int c=0; //素数计数 8 memset(vis,0,sizeof(vis)); 9 vis[1]=1; //1既不是素数也不是合数 10 for(int i=2;i<=n;i++){ 11 if(!vis[i]){ //将素数记录在prime数组 12 prime[++c]=i; 13 } 14 for(int j=1;j<=c&&i*prime[j]<=n;j++){ 15 vis[i*prime[j]]=1; 16 if(i%prime[j]==0){ //保证每个合数只会被它的最小质因数筛去 17 break; 18 } 19 } 20 } 21 } 22 int main(){ 23 Init(maxn); 24 return 0; 25 }
其中 prime[] 数组中的素数是递增的,当i能整除 prime[j],那么 i*prime[j+1] 这个合数肯定可以被 prime[j] 乘以某个数筛掉。因为 i=k*prime[j],那么 i*prime[j+1]=k*prime[j]*prime[j+1]=(k*prime[j+1])*prime[j]=k'*prime[j],接下来的素数同理,所以不用筛下去了。