淺談質數篩法
質數的定義
如果一個整數只有 \(1\) 和它本身這兩個因數,那麼這個數就是一個質數。
同時,如果一個整數 \(>1\) 且它不是質數,那麼我們稱這個數是合數。
\(0,1\) 這兩個數既不是質數,也不是合數。
1. 試除法
如果給定一個數,判斷它是不是質數,那麼只判斷 \([2,n-1]\) 中的任意一個是否能整除它,如果可以那就是合數,否則是質數。
這裡有一個優化:如果 \(n\) 是一個合數,那麼 \(n\) 一定包含一個非 \(1\) 的 \(<\sqrt{n}\) 的因數。
證明可以用反證法:如果 \(n\) 是一個合數,且 \(n=p\times q(p,q>\sqrt{n})\)
程式碼如下:
inline bool isprime(int n) {
if (n == 0 || n == 1) return false; //這裡記得特判
for (int i = 2;i * i <= n; i++)
if (n % i == 0) return false;
return true;
}
判斷一次的時間複雜度是 \(\mathcal{O}(\sqrt{n})\) ,如果要判斷 \([1,n]\) 中有哪一些是質數,那麼時間複雜度是 \(\mathcal{O}(n\sqrt{n})\)
埃氏篩法
試除法一個一個判斷太慢了,我們可以考慮這樣做:
對於每一個數 \(x\) , \(2x,3x,4x,5x\dots\) 一定都不是質數,可以一遍把全部的都篩掉。
也就是說,對於每一個質數(不用管合數的原因是合數的倍數會被質數篩掉),列舉所有小於 \(n\) 的倍數,那些倍數都不是質數。
跟上面試除法一樣,不需要從 \(2\) 列舉到 \(n\) ,因為每一個合數一定有一個小於 \(\sqrt{n}\) 的質因子,所以列舉到 \(\sqrt{n}\) 就可以了。
根據這個思路,寫出程式碼:
bool isprime[N]; inline void Prime(int n) { memset(isprime ,true ,sizeof(isprime)); isprime[0] = isprime[1] = false; for (int i = 2;i * i <= n; i++) if (isprime[i]) for (int j = 2;j * i <= n; j++) isprime[i * j] = false; }
時間複雜度為 \(\mathcal{O}(\dfrac{n}{2}+\dfrac{n}{3}+\dfrac{n}{5}+\dfrac{7}{2}+\cdots)=\mathcal{O}(n\log\log n)\) 。
線性篩/尤拉篩:
埃氏篩還可以優化:有一些數被多次標記了,比如 \(12\) ,在列舉 \(3\) 的倍數的時候算了一次,在列舉 \(2\) 的倍數的時候也算了一次。
線性篩/尤拉篩優化的思路就是:每次只列舉 \(i\) 的質數倍,同時每一個數字只能被它最小的質因子篩一次。
程式碼如下:
int prime[N] ,cnt; bool isprime[N];
inline void Prime(int n) {
memset(isprime ,true ,sizeof(isprime));
for (int i = 2;i <= n; i++) {
if (isprime[i]) prime[++cnt] = i;
//注意這裡沒有限制只能篩質數的倍數
for (int j = 1;j <= cnt && prime[j] * i <= n; j++) {
isprime[prime[j] * i] = false;
if (i % prime[j] == 0) break;
}
}
}
因為每個數字只會被篩一次,所以時間複雜度是 \(\mathcal{O}(n)\) 。