Eular質數篩選方法
1.有時候我們給出長度為N的區間需要求解出這個區間中質數的個數,假如使用素數定理來求解出的話時間複雜度可能有點高,因為使用素數定理的時候核心程式碼如下:
for(int i = 2;i < x; i++){
if(arr[i]!=0){
continue;
}
int k = 2;
while(i * k < x){
arr[i * k] = -1;
k++;
}
}
上面的程式碼可能存在重複標記的情況,因為迴圈到某個數字為質數的情況下,那麼會進入下面的while迴圈,k從2開始計算那麼假如當迴圈到質數7的時候進入while迴圈然後再將arr[ 14 ], arr[ 28 ]... 標記為-1 而arr[ 14 ]原來當質數為2的時候就標記過那麼就可能存在著多個標記重複的情況,所以當區間比較大的情況下那麼使用素數定理來解決的時候時間複雜度是比較高的,那麼我們可以使用另外一種方法來篩選素數從而降低在篩選素數的時間複雜度,這種方法是:Eular素數篩選定理
與Eratosthenes篩法不同的是,對於外層列舉i,無論i是質數,還是是合數,我們都會用i的倍數去篩。但在列舉的時候,我們只列舉i的質數倍。比如2i,3i,5i,...,而不去列舉4i,6i...,原因我們後面會講到。
此外,在從小到大依次列舉質數p來計算i的倍數時,我們還需要檢查i是否能夠整除p。若i能夠整除p,則停止列舉。
利用該演算法,可以保證每個合數只會被列舉到一次。我們可以證明如下命題:
假設一個合數k=M*p1,p1為其最小的質因子。則k只會在i=M,primeList[j]=p1時被篩掉一次。
首先會在i=M,primeList[j]=p1時被篩掉是顯然的。因為p1是k的最小質因子,所以i=M的所有質因子也≥p1。於是j迴圈在列舉到primeList[j]=p1前不會break,從而一定會在i=M,primeList[j]=p1時被篩掉
其次不會在其他時候被篩掉。否則假設k在i=N, primeList[j]=p1時被篩掉了,此時有k=N*p2。由於p1是k最小的質因子,所以p2 > p1,M > N且p|N。則i=N,j列舉到primeList[j]=p1時(沒到primeList[j]=p2)就break了。所以不會有其他時候篩掉k。
同時,不列舉合數倍數的原因也在此:對於一個合數k=M*2*3。只有在列舉到i=M*3時,才會計算到k。若我們列舉合數倍數,則可能會在i=M時,通過M*6計算到k,這樣也就造成了多次重複計算了。
綜上,Eular篩法可以保證每個合數只會被列舉到一次,時間複雜度為O(n)。當N越大時,其相對於Eratosthenes篩法的優勢也就越明顯
當迴圈到某個數為質數的時候那麼該質數通過迴圈遍歷乘以存放primeList陣列中原來存放的質數來標記處區間N中的合數
2. 具體的程式碼如下:
import java.util.Scanner;
public class Main{
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
long res = solve(n);
System.out.println(res);
}private static long solve(int n) {
int prime[] = new int[n + 1];
int primeCount = 0;
for(int i = 2; i <= n; i++){
if(prime[i] == 0){//存放質數方便後面標記合數
prime[primeCount++] = i;
//System.out.println(i);
}
for(int j = 0; j <= primeCount && i * prime[j] <= n; j++){
prime[i * prime[j]] = 1;
if(i % prime[j] == 0)break;
}
}
return primeCount;
}
}