質數篩
閑談
原因
蕪湖,蒟蒻的第十篇博客。(\(NOIP\)加油!!!)
背景
之前一直很想學習這里但是沒有抽出時間,今天身體不適待在家中,就趁機學習了一下。
質數篩
背景
我們在信息競賽的題目當中,很多時候會看到和質數相關的問題,我們如果用傳統的遍歷法的話,時間復雜度為\(O(\sqrt{n})\),並且一次只能求解一個。
bool isprime(int n){
for(int i = 2;i <= sqrt(n); i++)
if(n % i == 0) return false;
return true;
}
但我們往往會遇到求解一個區間內的質數總數的時候,這時候往往會造成太大的時間復雜度,質數篩就是一種可以將時間復雜度降低為線性的優秀方法。
理解
首先我們來了解一下什么叫做質數篩,首先我們開一個\(bool\)類型的數組,將它的大小定義為我們所需要求范圍中的最大值,並將它們都賦值為真\(is_prime[maxn] = true;\),這個數組中的元素如果為\(true\)那么說明這個數為質數,如果為\(false\)那么說明便不為質數,如何將合數找出來便非常的關鍵了。
埃拉托斯特尼篩法
這個過程是這樣實現的,首先從\(1\)開始,\(1\)既不是質數也不是合數,所以標記為\(false\);接着是\(2\),\(2\)是質數,便標記為\(true\),注意將\(2\)到\(maxn\)中所有能被\(2\)整除的數都標記為\(false\);接着是\(3\),標記為\(true\),將到\(maxn\)能被\(3\)整除的數標記為\(false\);接下來,我們發現\(4\)已經被標記為\(false\)了,所以我們跳過它,將沒有被標記過的\(5\)標記為\(true\),將到\(maxn\)能被\(5\)整除的數標記為\(false\);並一直做下去,就會把不超過\(false\)的合數全部標記為\(0\),不超過\(maxn\)的質數全部標記為\(true\)。因為這個過程最開始是希臘人是把數寫在塗臘的板上,每要划去一個數,就在上面記以小點,尋求質數的工作完畢后,這許多小點就像一個篩子,並且是由埃拉托斯特尼發明的,所以叫做“埃拉托斯特尼篩法”,簡稱“篩法”。
int prime[MAXN]; //素數數組
bool isprime[MAXN + 10]; //is_prime[i]表示i是素數
//返回n以內素數的個數
int solve(int n){
int p = 0; //素數個數計數器
for (int i = 0; i <= n; i++)
is_prime[i] = true;
is_prime[0] = is_pri[1] = false; //首先標記0和1不是素數
for (int i = 2; i <= n; i++){
if (is_prime[i]){ //如果i是素數
prime[++p] = i; //將素數放進素數表
for (int j = 2 * i; j <= n; j += i) //所有i的倍數都不是素數
is_prime[j] = false;
}
}
return p;
}
但是我們會發現,例如\(6\)這個數字,會被\(2\)和\(3\)重復篩去一遍,會造成不必要的計算,時間復雜度是\(O(nlognlogn)\)所以我們將這個方法進行了改進。
歐拉篩
歐拉篩的核心是:讓每一個合數被最小質因數篩去。我們來看一個具體的例子。
這次我們還運用了一個\(**質數表**\)prime[maxn]$來維護,首先把\(2\)把它加入到質數表當中,並在原數組中刪除;
並且用\(2\)來乘質數表中的每一個數,這里只有\(2\),所以得出的是\(4\),所以我們將\(4\)在原數組中划去;
接下來是\(3\),我們也同樣將 它加入到質數表中,並在原數組中刪除;
同時與質數表中的每一個數字相乘,得到\(6\)和\(9\),將它們划掉;
接下來是\(4\)(注意\(4\)也是需要遍歷的,只是不加入到質數表當中而已),我們划掉\(8\),但是不划掉\(12\),因為我們說歐拉篩的核心是讓每一個合數被最小的質因數篩去,應該用\(2\)去篩去\(12\)。
我們分析,對於一個數\(x\),我們遍歷到質數表中的\(p\)時,如果發現\(p|x\),那么我們就應該停止遍歷整個質數表。我們來證明一下:設\(x = pr(r \geq p)\)(\(p\)為\(x\)的最小質因數),那么對於任意的\(p' > p\),有\(p'x = pp'r = p(p'r)\),即說明\(p'x\)的最小質因數不是\(p'\),不應該在這里划掉。
按照這個思路我們可以得到。
代碼如下
int cnt = 0;
bool isprime[MAXN];
int primes[MAXN];
void solve_plus(int n){
for (int i = 2; i <= n; i++){
if (!isprime[i])
primes[++cnt] = i;
for (int p = 1; p <= cnt; p++){
if (p * i > n)
break;
isprime[p * i] = 1;
if (i % p == 0)
break;
}
}
}
那么這就是我們在信息競賽中最常用到的兩種質數篩了,多多敲代碼更有助於理解。
完結撒花ヾ(✿゚▽゚)ノ