在傳統的素數篩法中,我們使用了對於每一個數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;
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[])
{
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]<<" ";
return 0;
}