质数筛

质数筛

闲谈

原因

芜湖,蒟蒻的第十篇博客。(\(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;
        }
    }
}

那么这就是我们在信息竞赛中最常用到的两种质数筛了,多多敲代码更有助于理解。
完结撒花ヾ(✿゚▽゚)ノ

posted @ 2020-11-19 19:47  summitsoul  阅读(925)  评论(0编辑  收藏  举报