『素數(Prime)判定和線性尤拉篩法(The sieve of Euler)』
<更新提示>
<第一次更新>
<正文>
素數(Prime)及判定
定義
素數又稱質數,一個大於1的自然數,除了1和它自身外,不能整除其他自然數的數叫做質數,否則稱為合數。
1既不是素數也不是合數。
判定
如何判定一個數是否是素數呢?顯然,我們可以列舉這個數的因數,如果存在除了它本身和1以外的因數,那麼這個數就是素數。
在列舉時,有一個很簡單的優化:一個合數nn必有一個小於等於n−−√n的因數。
證明如下:
假設一個合數nn沒有小於等於n−−√n的因數。
由於nn為合數,所以除了nn與11以外,它至少還有兩個因數p1(p1>n−−√)p1(p1>n)和p2(p2>n−−√)p2(p2>n),滿足p1p2=np1p2=n。
與p1>n−−√,p2>n−−√p1>n,p2>n矛盾,故假設不成立。
所以我們得到了O(n−−√)O(n)效率的素數判定演算法。
Code:Code:
inline bool check(k)
{
for(int i=2;i*i<=k;i++)
if(k%i==0)return 0;
return 1;
}
篩法(Sieve)求素數
現在有一個新的問題模型,如果我們需要求解1−n1−n的所有素數,那麼直接用判定法效率顯然太低了。我們需要更高效率的演算法,由此我們引入篩法。
埃氏篩法(The sieve of Eratosthenes)
這是篩法思想的基本模型。根據算數基本定理,我們得知:
k=pa11⋅pa22⋅...⋅pakk
k=p1a1·p2a2·...·pkak
即任意一個數kk都是由若干素數相乘得到的。
那麼我們可以列舉2−n2−n的每一個數,如果這個數沒被標記,則說明這個數是素數,記錄這個數,並標記這個數的所有倍數不是素數。
那麼這樣就可以求解1−n1−n的所有素數了。時間複雜度為O(n ln(ln n))O(n ln(ln n))。
實現
這就是OI競賽中最常用的素數求解演算法了,實現也非常簡單。
Code:Code:
#include<bits/stdc++.h>
using namespace std;
int cnt=0,n,flag[100080]={},Prime[100080]={};
inline void sieve(void)
{
for(int i=2;i<=n;i++)
{
if(!flag[i])Prime[++cnt]=i;else continue;
for(int j=i*2;j<=n;j+=i)flag[j]=true;
}
}
int main(void)
{
cin>>n;
sieve();
for(int i=1;i<=cnt;i++)cout<<Prime[i]<<" ";
cout<<endl;
}
尤拉篩法(The sieve of Euler)
尤拉篩法就是基於埃氏篩法的優化。
在模擬埃氏篩法的過程中,我們不難發現有很多合數會被它的各個素因子篩好幾次,我們可以基於這種情況進行優化:每個合數必有一個最小素因子,用這個因子篩掉合數
所以,我們直接利用之前求出的素數進行篩數,如果發現當前這個數已經是之前某個素數的倍數時,那就說明這個數在以後會由某個更大的數乘以這個小素數篩去,同理,之後的篩數也是沒有必要的,這時候就可以跳出迴圈了。
這樣,我們就能保證每一個數只被篩一次,就實現了線性時間複雜度的篩法。
實現
尤拉篩法和埃氏篩法大體相似,但細節有所不同,注意不要搞混。
Code:Code:
#include<bits/stdc++.h>
using namespace std;
int cnt=0,n,flag[100080]={},Prime[100080]={};
inline void seive(void)
{
for(int i=2;i<=n;i++)
{
if(!flag[i])Prime[++cnt]=i;
//注意,這裡沒了continue,因為在篩某個數時需要用到它的最大因數,而這個數可能是個合數,所以不管是素數還是合數,都要執行以下的篩數過程
for(int j=1;j<www.michenggw.com =www.mcyllpt.com cnt&&i*Prime[j]<=n;j++)
{
flag[i*Prime[j]]=1;
if(i%Prime[j]==0)break;
}
}
}
int main(void)
{
cin>>n;
seive(www.gcyL157.com);
for(int i=1;i<=cnt;www.mhylpt.com/ i++)cout<<Prime[i]<<" ";
cout<<endl;
}