文章目錄
- 代碼1,這個有點長,但是很好理解
- 代碼2:難理解一點
上一篇一道題目用到了篩選素數,不過是那個只需要求3400以內的,比較少,無所謂,關鍵是如果要求1000000以內的,再直接求就不行了,太慢了。
下面給出兩個自己用的模版,並解釋解釋,為何這樣能夠很快計算出素數。
代碼1,這個有點長,但是很好理解
#include <stdlib.h>#include <stdio.h>#include <string.h>int prime[100000];bool s[1000010];void Prime()//求素數{ int i,j,k,t; for (i=2;i<=1000000;i+=2) s[i]=0; for (i=1;i<=1000000;i+=2) s[i]=1; s[1]=0; s[2]=1; for (i=3;i<=1010;i+=2)//如果要求10000以內的素數,你至少要看i%sqrt(10000)都不可約才行,這也是為什麼1000000以內只要i<=1000就行了的道理 { if (s[i]) { k=2*i;//應為所有偶數已經剔除,所以此處t=3*i(相當於)也就是此次剔除的仍是奇數,所以避免了重複剔除偶數,速度快。 t=i+k; while (t<=1000000) { s[t]=0; t=t+k; } } } k=1; prime[1]=2; for (i=3;i<=1000000;i+=2) { if (s[i]==1) { k++; prime[k]=i; } } prime[0]=k; }
上面這個實際上就是先踢出了所有偶數,然後呢,以後要踢出的只能是奇數,否則你就重複預算了。k=2*i 是個偶數,i是奇數,3*i也是奇數,那麼每次t=i + k都是奇數。所以每次去除的都是奇數倍了。
這樣處理後基本上每個數只判斷一次就知道是不是素數了,所以是線性。。。
代碼2:難理解一點
const int M = 3000500;int p[400010], pNum;bool f[M];void Prime(){int i, j;for(i = 2; i < M; i++) {if(!f[i]) { p[pNum++] = i; }for(j = 0; j < pNum && p[j] * i < M; j++ ) {f[p[j]*i] = 1;if(!(i%p[j]))break;}}}
這個其實和上面那個是同一個道理。首先f[i]==0就是素數,從2開始。然後我們要去除已經取得素數的i倍的數,f[p[j]*i] = 1;
關鍵就是下面那句話不好理解,因為正是那句跳出迴圈才能保證篩選素數是線性。
為什麼i能除盡p[j]這個素數就跳出呢?
其實可以簡單證明一下:
合數可以由一個質數數與另一個數相乘得到
而同時假設合數a=質數b×質數c×一個數d
令e=c × d,假設b ≥ c,e為合數,令f=d × b
a=f × c ,其中c
即大的質數和該合數的乘積可用一個更大的合數和比其小的質數相乘得到
這也是if(!( i % prime[j]))break;的含義,這也是線性篩法算質數表的關鍵所在
舉個例子:
比如i = 9,現在素數是2 3 5 7
進入第二重迴圈了,f[2 * 9] = 1;f[3 * 9] = 1;
這個時候9%3==0,要跳出了,為什麼不做f[5* 9] =1;呢?
因為5 * 9 可以用3 * 15來代替,如果這個時候你計算了,那麼到i=15的時候這個數還會被重複計算一次,所以這裡大量避免了重複運算,所以也就節省了時間。
這裡總結一句話就是,一個大的合數和這個能除盡的質數的乘積,一定能被一個比起小的質數和合數更大的合數乘積來代替。
不懂的時候想想 5*9 = 5*3*3 = 3*15就是這個道理。