素數篩法講解


首先看一看判斷素數的方法,就是看一個數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 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM