欧拉前向筛法求素数
欧拉线性前向筛法求素数
时间复杂度:O(N)
先看代码,再进行解释
const int N=1e6+10;
int primes[N];
bool st[N];
void get_primes()
{
int cnt=0;
//1不是质数也不是合数
for(int i=2;i<=N;i++){
if(!st[i]) primes[++cnt]=i;//没有被筛去,说明是质数
for(int j=1;primes[j]*i<=N;j++){ //前向遍历素数
st[i*primes[j]]=true;//筛去合数
if(i%primes[j]==0) break;//核心操作,保证了O(n)的复杂度
}
}
}
引理:算术基本定理——任何一个大于1的自然数N,如果N不为质数,那么N可以唯一分解成有限个质数的乘积
非常重要:1既不是质数也不是合数
-
显然,在所有大于0的自然数中,除了质数就是合数.要求质数,只需要"筛"去所有的合数即可.如何筛去合数,这里就应用到了算术基本定理.关于合数,这里我们需要注意两点:
(1).所有合数都要筛到
(2).不能有重复筛选,否则无法达到在线性时间内的运行了 -
要做到第一点,根据算术基本定理:n=q*m(q表示最小质数,m=N/q,显然m>=q),对N内的m和q进行枚举
for(某个数m;m<=N;m++){ for(小于或等于m的质数q;q*m<=N;){...} }
这样我们就保证了能枚举N以内的所有整数,然而我们还不能保证枚举的合数不重复。
先来思考一下为什么会有重复,由于对于任意一个n=m*q
,m和q是相对的两个状态,m大了q就变小,m小了q就变大。
设n=m1*q1=m2*q2,q1<=q2<=m2<=m1我们这里需要保证当且仅当在q1是最小质因数时能求解,也就是去除q2和m2的情况.因为q1与q2互质,且q1<q2,则有m2%q1=0;
-
举例说明:我们构造一个数n可以同时被两个质数整除,如:
6=2*3=3*2
,我们既可以用2去筛,也可以用3去筛,即i=3,j=1,primes[j]=2
的情况,对应了标题的前向筛法证明:
#include <iostream> #include <cstring> #include <cstdio> #include <algorithm> using namespace std; const int N=1e6+10; int primes[N]; bool st[N]; void get_primes() { int cnt=0; //1不是质数也不是合数 for(int i=2;i<=N;i++){ if(!st[i]) primes[++cnt]=i;//没有被筛去,说明是质数 for(int j=1;i*primes[j]<=N;j++){ if(i*primes[j]==6){ cout << "i:" << i << " primes[j]:" << primes[j] << endl; } st[i*primes[j]]=true;//筛去合数 if(i%primes[j]==0) break;//核心操作,保证了O(n)的复杂度 } } } int main() { //n以内的所有质数 int n,cnt; cin >> n; get_primes(); for(int i=1;primes[i]<=n;i++){ cnt++; } cout << cnt << endl; return 0; } /* 输出 i:3 primes[j]:2 */
-
所以关键步骤来了:
if(i%peimes[j]==0) break;
当i是primes[j]的倍数时直接跳出循环,设i=primes[j]*t
,此时有i*primes[j+1]=primes[j]*t*primes[j+1]
,
可以看到此时的最小质数时primes[j],为了避免与当m=primes[j+1]*t
时有重复,这里可以通过break跳过计算
此外还需注意:要保证m从2开始,因为1*q=1
且i%1==0
;
注:
第二个for循环的判断条件不需要的j<cnt
(1).i是质数,那么当j=cnt时break
(2).i是合数,那么必然在最下素数处也会break