首先看一看判斷素數的方法,就是看一個數n能否被2~n-1內的數整除,如果能整除就不是素數,反之則是,直接上優化后的代碼:
1 bool isprime(int x) 2 { 3 for(int i=2;i<=sqrt(x);i++) 4 { 5 if(x%i==0) 6 return false; 7 } 8 return true; 9 }
對於一些題目,需要判斷的素數非常大且多,用這種方法的話時間復雜度是絕不允許的,因此就有了素數篩法,顧名思義,是提前把素數篩選出來,這樣之后判斷的時候就快了。
先說一下素數篩法的原理:
從第一個素數2開始,將所有2的倍數篩掉,因為2的倍數一定不是素數,然后往后,3的倍數篩掉.....一直到只有素數剩下,如圖,黃色的數為素數(圖片來源於網絡)
這種篩法有兩種算法:
一是Eratosthenes(埃拉托斯特尼)篩法,這個篩法是直接把原理應用上了,從第一個素數開始,只要是自身倍數的,全都檢查一遍並篩掉。
代碼如下:primelist[ ]代表素數表,isprime[ ]代表是否是素數。
1 bool isprime[maxn+5]; 2 void MakePrimeList() 3 { 4 int cnt=1; 5 memset(isprime,true,sizeof(isprime));//先假設所有數是素數 6 for(int i=2;i<=maxn;i++)//將所有數遍歷 7 { 8 if(isprime[i])//判斷是否素數 9 primelist[++cnt]=i;//是的話加入素數表 10 for(int j=i+i;j<=maxn;j+=i)//從這個數的最小的倍數開始 11 { 12 isprime[j]=false;//標記為非素數 13 } 14 // for(j=2;j*i<=maxn;j++)這是原型,上面是改進的 15 // { 16 // isprime[i*j]=false; 17 // } 18 } 19 return ; 20 }
這種埃氏篩法時間復雜度氏O(nlognlogn),顯然時間復雜度仍然較大。
二是Euler(歐拉)篩法,埃氏篩法復雜度高的原因是很多數都被重復篩了好幾次甚至很多次,做了很多不必要的工作,而歐拉篩法就避免了重復篩數。
先看代碼:
1 void MakePrimeList() 2 { 3 int cnt=0; 4 memset(isprime,true,sizeof(isprime));//先假設所有數是素數 5 for(int i=2;i<=maxn;i++)//將所有數遍歷 6 { 7 if(isprime[i])//判斷是否素數 8 primelist[++cnt]=i;//是的話加入素數表 9 for(int j=1;j<=cnt;j++)//這里是從素數表的已知是素數的第一個數開始,到已知數目結束 10 { 11 if(i*primelist[j]>maxn) 12 break; 13 isprime[i*primelist[j]]=false;//這樣循環下去,就把所有非素數找到並標記了 14 if(i%primelist[j]==0)//這里和關於j的for循環是關鍵,保證不重復篩數 15 break; 16 } 17 } 18 return ; 19 }
百度上關於if(i%primelist[j]==0)有這樣解釋:(這里prime[]相當於primelist[])
prime[]數組中的素數是遞增的,當i能整除prime[j],那么i*prime[j+1]這個合數肯定被prime[j]乘以某個數篩掉。
因為i中含有prime[j],prime[j]比prime[j+1]小,即i=k*prime[j],那么i*prime[j+1]=(k*prime[j])*prime
[j+1]=k’*prime[j],接下去的素數同理。所以不用篩下去了。因此,在滿足i%prime[j]==0這個條件之前以及第一次
滿足改條件時,prime[j]必定是prime[j]*i的最小因子。
舉個例子:從第一個素數2開始,此時 i 等於2,i*2=4(始終是從j=1即第一個素數開始乘以 i,下同),4不是素數,標記,i%2==0,停止。i++;
第二個數也是第二個素數3,此時 i 等於3,i*2=6,6被標記不是素數,i%2!=0,i*3=9,9被標記,i%3==0,停止。i++;
第三個數4不是素數,不加入表, 此時 i 等於4,i*2=8,8被標記,i%2==0,停止,i++;
.......等等..
它始終是把離當前素數最近的數篩掉,而且不重不漏,時間復雜度是o(n),確實被證明過是O(n)。
例題:lightoj1259 - Goldbach`s Conjecture
題意就是給你一個數,讓你把它拆成兩個素數數相加,問有多少種方法(a<b就是不能重復)
直接在素數表中找判斷即可。
代碼如下:
1 #include<iostream> 2 #include<cstring> 3 #include<cstdio> 4 #include<cmath> 5 using namespace std; 6 const int maxn=10000000; 7 int primelist[maxn/10]; 8 bool isprime[maxn+5]; 9 void MakePrimeList() 10 { 11 int cnt=0; 12 memset(isprime,true,sizeof(isprime)); 13 for(int i=2;i<=maxn;i++) 14 { 15 if(isprime[i]) 16 primelist[++cnt]=i; 17 for(int j=1;j<=cnt;j++) 18 { 19 if(i*primelist[j]>maxn) 20 break; 21 isprime[i*primelist[j]]=false; 22 if(i%primelist[j]==0) 23 break; 24 } 25 } 26 return ; 27 } 28 int main() 29 { 30 int n,t; 31 int cas=0; 32 cin>>t; 33 MakePrimeList(); 34 while(t--) 35 { 36 int num=0,cnt=1; 37 scanf("%d",&n); 38 for(int i=1;primelist[i]<=n/2;i++)//從素數表中第一個數到大小為n/2是因為a<b 39 { 40 if(isprime[n-primelist[i]]) 41 num++; 42 } 43 printf("Case %d: %d\n",++cas,num); 44 } 45 return 0; 46 }