素數個數-尤拉篩法
模擬的時候真沒想到這是一道這麼麻煩的題。。。
先來看題:
素數個數
題目描述
求1,2,\cdots,N1,2,⋯,N 中素數的個數。
輸入輸出格式
輸入格式:1 個整數N。
輸出格式:1 個整數,表示素數的個數。
輸入輸出樣例
輸入樣例#1: 複製10輸出樣例#1: 複製
4
說明
• 對於40% 的資料,1 \N \10^6
• 對於80% 的資料,1 \lN \10^7
• 對於100% 的資料,1 \N \10^8
當看到這道題的時候,我直接寫下了逐個取餘判斷的程式碼,然而看到資料範圍的時候我就慌了。。。
當時由於沒學過關於素數篩選的演算法,很顯然除了打表我什麼也幹不了。。。
好吧,不廢話了,直接步入正題:
由於這道題的資料範圍較大,因此列舉肯定是行不通的,就算是使用正常篩法,面對10的八次方的資料也是顯得十分的吃力,由此,我們要引入尤拉篩法(這裡先介紹一下樸素篩法):
由於任何合數都可以拆分成若干素數的乘積,因此每當我們找到一個素數的時候,就可以將部分合數篩選出來,那麼我們就解決的部分的問題。
並且講到這裡有一個隱含條件:就是沒有被篩選出來的數就是素數(這個可以簡化程式碼長度,也是理解這道題的關鍵),至於證明,我們可以用反證法:如果它不是素數,那麼它一定是合數,而合數又可以拆分成兩個素數的乘積,那麼在找到它的因子的時候就一定會將其篩選出來。 證完
強忍著沒有在證明的時候說顯然。。。
但是,可能大家在證明的時候會發現一個問題:就是一個合數一次拆分時的素數因子可能不止一個,那麼不就重複計算了嗎?
因此我們要優化!!!(由此誕生了尤拉篩法)
由於下面講解問題,我們先看程式碼:
1 #include<cstdio> 2#include<cmath> 3 int prime[100000005]; 4 bool vis[100000005]; 5 int Prime(int n) 6 { 7 int ans=0; 8 for(int i=2; i<=n; i++) 9 { 10 if(!vis[i]) 11 prime[ans++]=i; 12 for(int j=0; j<ans&&i*prime[j]<=n; j++) 13 { 14 vis[i*prime[j]]=1; 15 if(i%prime[j]==0) 16 break; 17 } 18 } 19 return ans; 20 } 21 int main() 22 { 23 int n; 24 scanf("%d",&n); 25 printf("%d",Prime(n)); 26 return 0; 27 }
其實也很好理解:由於合數可以拆分成不同素數*k(k∈Z),那麼當我們篩選時只要篩選出最小素數因子即可,比如12
12=4*3=6*2 很顯然,我們需要的就是用素數2來篩選掉12,那麼怎麼實現呢?
其實這其中有一個規律:由於i是由小到大列舉的,並且陣列中的素數也是由小到大列舉的,那麼顯然我們會先看到4*3,又因為4在和素數2篩掉8時發現竟然能被整除,並且接下來的4*3是沒有意義的計算,那麼每當i%素數==0是跳出即可。
如果你要問我具體該如何證明那我可以簡單說一下:
由於p[j]*k(k∈Z)=i,那麼a[j+1]*i=a[j]*k*a[j+1],所以a[j]乘以某個數一定在將來會把這個合數篩掉,由於a[j]比a[j+1]小,那麼a[j]才可能是最小素數因子。
好的,我講完了(好長啊qwq)