在传统的素数筛法中,我们使用了对于每一个数n,在 1~(√n) 范围内进行取模检查,这样逐一判断的复杂度为n(√n)。 但如果我们需要更快的筛法时怎么办? 于是著名的 欧拉筛 诞生了。
在传统的素数筛法中,我们使用了对于每一个数n,在 1~(√n) 范围内进行取模检查,这样逐一判断的复杂度为n(√n)。
但如果我们需要更快的筛法时怎么办?
于是著名的欧拉筛诞生了。它能将复杂度降为O(n)级别。
1.关键理解:
欧拉筛的原理是保证在 2~n 范围中的每一个合数都能被唯一分解成它的最小质因数与除自己外最大的因数相乘的形式。因此我们枚举2~n中的每一个数作为筛法中的“除自己外的最大因数”,如果它未被标记为合数,就先将它放入素数表内,再将这个最大因数与素数表中已经找到的素数作为最小质因数相乘,将得到的这些数标记为合数。最后输出得到的素数表即可。
但是我们如何保证每个合数都被唯一分解?
解决方法如下:
当此时取出的素数表中的素数(即枚举的最小质因子)整除于当前枚举的合数时,我们就停止循环素数表,开始枚举下一个合数。
证明如下:
设当前枚举的最小质因子prime[i]整除于合数n时,即我们要筛掉合数 n*prime[i] ;如果我们此时不退出,继续枚举下一个素数prime[i+1],对于将要筛掉的合数 n*prime[i+1] 由于插入顺序从小到大,则 prime[i+1]>prime[i]。由于prime[i]整除于合数n,所以必然合数 n*prime[i+1] 还可以被分解为
\[(\frac{n}{prime[i]}*prime[i+1])*prime[i]\]
显然,在上面的分解方式中,我们将要筛掉的合数分解为更小的质因子 prime[i] ,这不符合我们对于每一个数被唯一分解的要求,所以我们可在代码中加入一行判断整除关系的代码进行优化。
2.代码实现:
#include <iostream> #include <algorithm> #include <cstdio> #include <cstdlib> #include <cmath> #include <vector> #include <stack> #include <set> #include <cstring> #include <string> #include <queue> using namespace std; #define file inline void read(int &x){ x=0;int f=1; char ch=getchar(); while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } x*=f; } bool IsPrime[100005]; int prime[50005]; int main(int argc, char const *argv[]) { #ifndef file char IN[105]=".in"; char OUT[105]=".out"; freopen(IN,"r",stdin); freopen(OUT,"w",stdout); #endif int n,top=1; memset(IsPrime,1,sizeof(IsPrime)); read(n); for(int i=2;i<=n;i++) { if(IsPrime[i]) prime[top]=i,top++; for(int j=1;j<top;j++) { if(i*prime[j]>n) break; IsPrime[i*prime[j]]=0; if(i%prime[j]==0) break; } } cout<<top-1<<endl<<endl; for(int i=1;i<top;i++) cout<<prime[i]<<" "; #ifndef file fclose(stdin); fclose(stdout); #endif return 0; }