素數的篩法

素數的篩法有不少種算法

在此給出常見的三種方法函數

 

如下給出的全部代碼均已經過這裏的測試測試

 

埃拉託斯特尼篩法

名字好長 :joy:  不過代碼很短優化

思路很是簡單,對於每個素數,枚舉它的倍數,它的倍數必定不是素數spa

這樣必定能夠保證每一個素數都會被篩出來code

還有,咱們第一層循環枚舉到$\sqrt(n)$就好,由於若是當前枚舉的數大於n,那麼它能篩出來的數必定在以前就被枚舉過blog

好比說:get

$\sqrt(100)=10$it

不難發現咱們從$20$枚舉所篩去的數必定被$5$篩過io

 1 #include<cstdio>
 2 #include<cmath>
 3 using namespace std;
 4 const int MAXN=10000001;
 5 inline int read()
 6 {
 7     char c=getchar();int f=1,x=0;
 8     while(c<'0'||c>'9')    {if(c=='-')    f=-1;c=getchar();}
 9     while(c>='0'&&c<='9')    x=x*10+c-48,c=getchar();return x*f;
10 }
11 int vis[MAXN];
12 int n,m;
13 int main()
14 {
15     n=read();m=read();
16     vis[1]=1;//1不是質數
17     for(int i=2;i<=sqrt(n);i++)
18         for(int j=i*i;j<=n;j+=i)
19             vis[j]=1;
20     while(m--)
21     {
22         int p=read();
23         if(vis[p]==1)    printf("No\n");
24         else             printf("Yes\n");
25     }
26     return 0;
27 }

 

可是你會發現這份代碼只能得30分

看來這種算法仍是不夠優秀

下面咱們來探索一下他的優化

另外,這種算法的時間複雜度:$O(n*logn)$

 

埃拉託斯特尼篩法優化版

根據惟一分解定理 

每個數均可以被分解成素數乘積的形式

那咱們枚舉的時候,只有在當前數是素數的狀況下,才繼續枚舉就好

這樣能夠保證每一個素數都會被篩出來

 

 1 #include<cstdio>
 2 #include<cmath>
 3 using namespace std;
 4 const int MAXN=10000001;
 5 inline int read()
 6 {
 7     char c=getchar();int f=1,x=0;
 8     while(c<'0'||c>'9')    {if(c=='-')    f=-1;c=getchar();}
 9     while(c>='0'&&c<='9')    x=x*10+c-48,c=getchar();return x*f;
10 }
11 int vis[MAXN];
12 int n,m;
13 int main()
14 {
15     n=read();m=read();
16     vis[1]=1;//1不是質數
17     for(int i=2;i<=sqrt(n);i++)
18         if(vis[i]==0)
19             for(int j=i*i;j<=n;j+=i)
20                 vis[j]=1;
21     while(m--)
22     {
23         int p=read();
24         if(vis[p]==1)    printf("No\n");
25         else             printf("Yes\n");
26     }
27     return 0;
28 }

 

 

 

 

果真,加了優化以後這種算法快了很多

能夠證實,它的複雜度爲:$O(n*log^{logn})$

這種算法已經很是優秀了,可是對於1e7這種極端數據,仍是有被卡的風險

那麼,還有沒有更快的篩法呢?

答案是確定的!

 

歐拉篩

咱們思考一下第二種篩法的運算過程

不難發現,對於6這個數,它被2篩了一次,又被3篩了一次

第二次篩顯然是多餘的,

咱們考慮去掉這步運算

 1 #include<cstdio>
 2 #include<cmath>
 3 using namespace std;
 4 const int MAXN=10000001;
 5 inline int read()
 6 {
 7     char c=getchar();int f=1,x=0;
 8     while(c<'0'||c>'9')    {if(c=='-')    f=-1;c=getchar();}
 9     while(c>='0'&&c<='9')    x=x*10+c-48,c=getchar();return x*f;
10 }
11 int vis[MAXN],prime[MAXN];
12 int tot=0;
13 int n,m;
14 int Euler()
15 {
16     vis[1]=1;
17     for(int i=2;i<=n;i++)
18     {
19         if(vis[i]==0)    prime[++tot]=i;
20         for(int j=1;j<=tot&&i*prime[j]<=n;j++)
21         {
22             vis[i*prime[j]]=1;
23             if(i%prime[j]==0)    break;
24         }
25     }
26 }
27 int main()
28 {
29     n=read();m=read();
30     Euler();
31     for(int i=1;i<=m;i++)
32     {
33         int p=read();
34         if(vis[p]==1)    printf("No\n");
35         else             printf("Yes\n");
36     }
37     return 0;
38 }

 

對於這份代碼,咱們分狀況討論

 

當$i$是素數的時候,那麼兩個素數的乘積必定沒有被篩過,能夠避免重複篩

 

當$i$不是素數的時候

程序中有一句很是關鍵的話

if(i%prime[j]==0) break;

 

 

若是咱們把$i$的惟一分解形式表示爲$i = p_1^{a_1}p_2^{a_2} \dots p_n^{a_n}$

這句話能夠保證:本次循環只能篩除不大於${p_1}*i$的數

這樣的話每一個數$i$都只能篩除不大於$i$乘$i$的最小素因子的數

反過來,每一個數只能被它的最小素因子篩去。

也就能夠保證每一個數只會被篩一次(這一步好像不是很顯然,我在最後會給出證實)

舉個例子,

設$i=2*3*5$,此時能篩去$i*2$,可是不能篩去$3*i$

由於若是能曬出$3*i$的話,

當$i_2=3*3*5$時,篩除$2*i_2$就和前面重複了

另外爲了方便你們直觀理解,給出一張圖表

 

 

這樣顯得直觀一些

你們好好揣摩揣摩

 

上面的證實:我本身瞎yy的可能不是很嚴謹

如今咱們須要證實$i = p_1^{a_1}p_2^{a_2} \dots p_n^{a_n}$只會被$p_1$篩去

那麼咱們須要證實三個條件

1.$i$必定被$p_1$和$p_1^{a_1 - 1}p_2^{a_2} \dots p_n^{a_n}$篩除過

很顯然,在枚舉到$p_1$以前不會有其餘素因子使$p_1^{a_1 - 1}p_2^{a_2} \dots p_n^{a_n}$中止循環

2.$i$不會被$p_1^{a_1}p_2^{a_2 - 1} \dots p_n^{a_n}$篩去

一樣也很顯然,當枚舉到$p_1$時就會中止循環

 

 

 

能夠看出這種算法的時間效率是很是高的!

時間複雜度:嚴格$O(n)$

 

總結

在通常狀況下,第二種篩法已經徹底夠用。

第三種篩法的優點不只僅在於速度快,並且還可以篩積性函數,像歐拉函數,莫比烏斯函數等。

這個我之後還會講的

相關文章
相關標籤/搜索