質數篩


質數篩

閑談

原因

蕪湖,蒟蒻的第十篇博客。(\(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)\)所以我們將這個方法進行了改進。

歐拉篩

歐拉篩的核心是:讓每一個合數被最小質因數篩去。我們來看一個具體的例子。

\[\large{2\quad3\quad4\quad5\quad6\quad7\quad8\quad9\quad10\quad11\quad12\quad13\quad14\quad15\quad16} \]

\[primes:() \]

這次我們還運用了一個\(**質數表**\)prime[maxn]$來維護,首先把\(2\)把它加入到質數表當中,並在原數組中刪除;

\[\large{\color{blue}2\quad3\quad4\quad5\quad6\quad7\quad8\quad9\quad10\quad11\quad12\quad13\quad14\quad15\quad16} \]

\[primes:(2) \]

並且用\(2\)來乘質數表中的每一個數,這里只有\(2\),所以得出的是\(4\),所以我們將\(4\)在原數組中划去;

\[\large{\color{blue}2\quad3\quad\color{red}4\quad5\quad6\quad7\quad8\quad9\quad10\quad11\quad12\quad13\quad14\quad15\quad16} \]

\[primes:(2) \]

接下來是\(3\),我們也同樣將 它加入到質數表中,並在原數組中刪除;

\[\large{\color{blue}2\quad\color{blue}3\quad\color{red}4\quad5\quad6\quad7\quad8\quad9\quad10\quad11\quad12\quad13\quad14\quad15\quad16} \]

\[primes:(2, 3) \]

同時與質數表中的每一個數字相乘,得到\(6\)\(9\),將它們划掉;

\[\large{\color{blue}2\quad\color{blue}3\quad\color{red}4\quad5\quad\color{red}6\quad7\quad8\quad\color{red}9\quad10\quad11\quad12\quad13\quad14\quad15\quad16} \]

\[primes:(2, 3) \]

接下來是\(4\)(注意\(4\)也是需要遍歷的,只是不加入到質數表當中而已),我們划掉\(8\),但是不划掉\(12\),因為我們說歐拉篩的核心是讓每一個合數被最小的質因數篩去,應該用\(2\)去篩去\(12\)

\[\large{\color{blue}2\quad\color{blue}3\quad\color{red}4\quad5\quad\color{red}6\quad7\quad\color{red}8\quad\color{red}9\quad10\quad11\quad12\quad13\quad14\quad15\quad16} \]

\[primes:(2, 3) \]

我們分析,對於一個數\(x\),我們遍歷到質數表中的\(p\)時,如果發現\(p|x\),那么我們就應該停止遍歷整個質數表。我們來證明一下:設\(x = pr(r \geq p)\)(\(p\)\(x\)的最小質因數),那么對於任意的\(p' > p\),有\(p'x = pp'r = p(p'r)\),即說明\(p'x\)的最小質因數不是\(p'\),不應該在這里划掉。
按照這個思路我們可以得到。

\[\large{\color{blue}2\quad\color{blue}3\quad\color{red}4\quad\color{blue}5\quad\color{red}6\quad\color{blue}7\quad\color{red}8\quad\color{red}9\quad\color{red}{10}\quad\color{blue}{11}\quad\color{red}{12}\quad\color{blue}{13}\quad\color{red}{14}\quad\color{red}{15}\quad\color{red}{16}} \]

\[primes:(2, 3, 5, 7, 11, 13) \]

代碼如下

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;
        }
    }
}

那么這就是我們在信息競賽中最常用到的兩種質數篩了,多多敲代碼更有助於理解。
完結撒花ヾ(✿゚▽゚)ノ


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM