判断一个数是否为两个素数乘积_算法学习笔记(17): 素数筛

c8056d35cbcaf5a9d33bb7eaec3cd64e.png

素数筛法,是一种快速“筛”出2~n之间所有素数的方法。朴素的筛法叫埃氏筛(the Sieve ofEratosthenes,埃拉托色尼筛),它的过程是这样的:

我们把2~n的数按顺序写出来:

从前往后看,找到第一个未被划掉的数,2,这说明它是质数。然后把2的倍数(不包括2)划掉:

下一个未被划掉的数是3,它是质数,把3的倍数划掉:

接下来应该是5,但是5已经超过

了,所以遍历结束,剩下未被划掉的都是素数:

如何?是不是比一个一个判断快多了?这个过程写成代码就是:

bool isnp[MAXN]; // is not prime: 不是素数
void init(int n)
{
    for (int i = 2; i * i <= n; i++)
        if (!isnp[i])
            for (int j = i * i; j <= n; j += i)
                isnp[j] = 1;
}

代码也很简洁。这个算法的复杂度是

,还是非常优秀了。但是我们可能会发现,在筛的过程中我们会重复筛到同一个数,例如12同时被2和3筛到,30同时被2、3和5筛到。所以我们引入
欧拉筛,也叫 线性筛,可以在
时间内完成对2~n的筛选。它的核心思想是:
让每一个合数被其最小质因数筛到

我们这次除了把2~n列出来,还维护一个质数表

还是从头到尾遍历,第一个数是2,未被划掉,把它放进质数表:

然后我们用2去乘质数表里的每个数,划掉它们:

下一个是3,加入质数表,划掉6、9:

下一个是4(注意这里划掉的数也要遍历,只是不加入质数表),先划掉8,但我们不划掉12,因为12

应该由它的最小质因数2筛掉,而不是3。

实际上,对于

,我们遍历到质数表中的
,且发现
时,就应当停止遍历质数表。因为:设
本身是
的最小质因数),那么对于任意
,有
,说明
的最小质因数不是
,我们不应该在此划掉它。

这么说有点抽象,具体地说,如这里的2能整除4,则

,那么对于任何大于2的质数
,都有
,其中2是一个比
更小的质数,所以
应该被2筛掉而不是被
筛掉。

下一个是5,加入质数表,划掉10,15:

下一个是6,划掉12,6被2整除,跳过。

……

按这样的步骤进行下去,可以筛掉所有的合数,并得到一张质数表。

我们可以保证每个合数都被筛过,设任意合数

,其中
的最小质因数,又设
的最小质因数。在处理
时,要遍历质数表,直到遇到
时才结束,所以任意小于等于
的质数与
的乘积,都会在此时被筛掉。

而由于一定有

(因为
的最小质因数是
,而不是
),所以在处理到
时,
一定会被筛到。

代码如下(C++11风格):

bool isnp[MAXN];
vector<int> primes; // 质数表
void init(int n)
{
    for (int i = 2; i <= n; i++)
    {
        if (!isnp[i])
            primes.push_back(i);
        for (int p : primes)
        {
            if (p * i > n)
                break;
            isnp[p * i] = 1;
            if (i % p == 0)
                break;
        }
    }
}

注意判断越界那里最好使用乘法而不是除法,一般不会溢出,计算机算除法比乘法要慢得多。

欧拉筛除了解决一些卡埃氏筛的毒瘤题(比如洛谷P3383,线性筛模板)外,在后续一些数论算法中还有更多的应用。


https://zhuanlan.zhihu.com/p/105467597​zhuanlan.zhihu.com
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值