素数的定义

只被1和自己两个正整数整除的正整数被称作素数,也被称作质数。

与之相反的数便是合数

合数的定义是:不仅被1和自己两个正整数整除,还被其他的正整数整除的正整数为合数。

Tips: 1不是素数,也不是合数

素数的判定

朴素算法

根据定义,我们可以这样做:枚举2n-1,如果枚举到的数整除了n,那n就不是质数,反之便是质数。

bool isPrime(int x)
{
    if(x < 2)                //   负数或者0、1都不是素数
        return false;
    for(int i = 2; i < n; i++)
        if(x % i == 0)
            return false;
    return true;
}

这个算法的复杂度是\(O(n)\)的,显然没有什么大用,我们想办法来优化一下。

加一点点优化

一个数的因数有这样一个性质:比如\(n = x \times y\),则其中有一个一定小于等于\(\sqrt{n}\),另一个一定大于等于\(\sqrt{n}\),这十分显然就不证明了。有关素数的一些算法_数组

所以我们只需要判断\(1\)到 \(\sqrt{n}\)中有没有数可以整除\(n\)就行了。

bool isPrime(int x)
{
    if(x < 2)
        return false;
    for(int i = 2; i * i < n; i++)
        if(x % i == 0)
            return false;
    return true;
}

那这个算法的时间复杂度是\(O(\sqrt n)\),这就实用得多啦。有关素数的一些算法_数组_02

一种十分神奇的算法!

Miller Robbin算法……

……

PTRS 赛普瑞斯正在链接中……

素数筛算法

在我们需要求出一段素数时,我们显然不能一个一个枚举,然后再判断,这样肯定超时,但是素数筛算法很好的解决了这个问题。

埃氏筛(埃拉托斯特尼筛法)

埃氏筛是一个希腊数学家提出的筛质数的方法,非常滴好用和好写。

先说思路:

从\(2\)开始往后判断,如果这个数还不是其他质数的倍数,那么这个数就是一个质数。

看图: 首先,1不是质数,先划掉。
有关素数的一些算法_数组_03

然后\(2\),他还不是其他质数的倍数,说明他是一个质数,就把他存到素数数组里面,然后把他的所有倍数都标记上。

有关素数的一些算法_线性筛_04

在看下一个数3,他不是任何质数的倍数,所以他是一个质数,把3的倍数标记上。

有关素数的一些算法_数组_05

下一个数是4,他已经被标记过了,所以4是一个合数,跳过。

……

以此类推,我们就可以吧1~18里所有的质数筛出来。

代码非常滴好写:

#define MAXN 100005
bool flag[MAXN];
int prime[MAXN];
int cnt = 0;
void getprime()
{
    for(int i = 2; i <= 100000; i++)
    {
        if(!flag[i])     //没有被标记过,是质数
        {
            cnt++;
            prime[cnt] = i;
            for(int j = i; j <= 100000; j += i)    //把这个质数的所有倍数标记上。
            {
                flag[j] = 1;
            }
        }
    }
}

这个算法的时间复杂度是\(O(n \times log\text{ }log\text{ }n)\)

非常滴快有关素数的一些算法_整除_06


欧拉筛(线性筛)

嘛,这是我知道最快的筛法(孤陋寡闻了有关素数的一些算法_素数筛_07),仔细看看上面的埃氏筛,我们会发现有的地方会被重复筛几次,比如\(6\),它会被\(2\)筛一次,也会被\(3\)筛一次,每次筛它花的时间很少,但是我们要找很大一串质数时,他就会积少成多,浪费许多时间,于是就出现了欧拉筛这种算法。

先看看代码:

#define MAXN 100005
int prime[MAXN];
bool flag[MAXN];
int cnt = 0;
void getprime()
{
    for(int i = 2; i <= 100001; i++)
    {
        if(!flag[i])
        {
            cnt++;
            prime[cnt] = i;
        }
        for(int j = 1; prime[j] * i <= 100001; j++)
        {
            flag[prime[j] * i] = 1;
            if(i % prime[j] == 0)
                break;
        }
    }
}

仔细看看,我们发现大概只多了这两行:

if(i % prime[j] == 0)
    break;

那这两行神奇的代码有什么用呢?为什么要加这两行就可以优化了呢?

有关素数的一些算法_数组_08

当\(i\)是\(prime_j\)的整数倍时,\(i \times prime_{j+1}\) 是 \(prime_j\) 的整数倍(因为\(i = x \times prime_j\)),不需要现在筛出,因为在之后这些数一定会被再筛一遍,所以break跳出循环就行了。

时间复杂度查不多是\(O(n)\)了有关素数的一些算法_数组

线性筛和其他思路的搭配

线性筛的好处就是在于筛选和筛选后可以统计其他东西 -------wch

仔细想想线性筛还能干些什么:

比如这个:
话说育母蜘蛛搞了一批小蜘蛛……于是育母蜘蛛要跟小蜘蛛们玩个游戏……育母蜘蛛让小蜘蛛们按照 1 到K 的顺序报数,报完K 后再报 1,每次报K 的小蜘蛛就会受到惩罚,小蜘蛛们在报数之前就 知道谁要受惩罚,于是一些小蜘蛛结成了同盟,小蜘蛛的同 盟很有意思,两只小蜘蛛是同盟当且仅当两只小蜘蛛的最大公约数不为 1。

现在育母蜘蛛要带一些小蜘蛛去参加展览,育母蜘蛛希望带的 小蜘蛛里任意两只都不是同盟,现在育母蜘蛛想知道它最多能带多少 只小蜘蛛,在个数最多的情况下,育母蜘蛛还想知道所带的小蜘蛛总 的编号和最小是多少。

这题我们就可以用一个a数组来存小蜘蛛的数量,sum数组来小蜘蛛的最小编号和。

int prime[MAXN],sum[MAXN],a[MAXN];
bool vis[MAXN];
int Prime(int n)  
{  
    int cnt = 0;  
    memset(vis, 0, sizeof(vis));  
    for(int i = 2; i < n; i++)  
    {
        a[i] = a[i- 1];
        sum[i] = sum[i - 1];
        if(!vis[i])
        {
            prime[cnt] = i;
            sum[i] += sum[i - 1] + i;
            a[i] = ++cnt;
        }
        for(int j = 0; j < cnt && i * prime[j] < n; j++)  
        {  
            vis[i * prime[j]] = 1;
            if(i % prime[j] == 0)
                break;  
        }  
    }
    return cnt;
}

还有一些其他的题目可以这样做,希望大家看了我的总结之后有所收获并且能够拓展延生。

\(The End\)