素數的篩法有不少種算法
在此給出常見的三種方法函數
如下給出的全部代碼均已經過這裏的測試測試
名字好長 :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)$
在通常狀況下,第二種篩法已經徹底夠用。
第三種篩法的優點不只僅在於速度快,並且還可以篩積性函數,像歐拉函數,莫比烏斯函數等。
這個我之後還會講的