- 不知名的东西:
[1,n]中的素数大约有
lnnn个。
背景
没有背景。
- 素数是数学中一种十分重要的数字,它的重要性使得它在信息学领域中也有广泛的应用。其中有一种很常见的题目就是:
- “给你一个数
i,判断其是否是素数。”
暴力
方法一
- 从素数的定义入手。
- 一个数
i为素数当且仅当
i的因数只有
1和
i。
- 因此我们可以从
2枚举到
i−1,如果
imod当前枚举的数=0则说明
i的因数不只有
1和
i,因此可以判断
i为合数。反之,如果除去
1和
i之外的其它数都不可以整除
i,则可以判定
i为质数。
- 时间复杂度:
O(n),其中n为判定的数。
方法二
- 发现对于一个数
i,如果
i=a∗b,则
a与
b中必然至少有一个数小于等于
i
。
- 因此我们可以得出,如果在
[2,i
]中找不到
i的因数,那么
[i
+1,i−1]中必然也不会有
i的因数。
- 因此把方法一改进一下,就可以做到
O(n
)的时间复杂度。
优化
- 有一个与它有关的重要定理:大于等于
5的一组素数肯定是
6x−1或
6x+1的形式,其中
x为正整数。
- 证明可以从一个大于3的素数的因数不包含
2、
3来考虑。
- 设待判定的数为
i,我们先判断
2,
3是不是
i的真因子,如果是就直接判为合数。
- 否则,则
i必含有
>=5的质因子,我们枚举
6x−1与
6x+1即可。
- 这样可以使时间复杂度缩小6倍。
(尽管好像没什么卵用)
筛法
- 筛法的不同之处在于,它一般会把所有素数筛出来,因此它的时间复杂度会比较高,但在需要得知
[1,n]中的素数时,筛法占有绝对的优势。
- 那些
O(n2)、O(nn
)的鬼畜筛,以及
O(n32)的神仙筛这里并不提及。
- 首先我们要知道,筛法的本质是用一个非
1的数把它的倍数(不包含它本身)给筛掉。因为很显然那些倍数绝对是合数。
普通筛
- 先定义一个bool数组
bz[i]代表
i是不是素数
- 从
1到
n枚举,然后枚举它的倍数,打上
false标记。
- 发现这样子时间复杂度是调和级数,是
O(nlog2n)的。
- 代码大概长这样子:
memset(bz,true,sizeof(bz));
for(int i=1;i<=n;i++)
for(int j=2;n/j>=i;j++) bz[i*j]=false;
- 发现memset会耗掉些许时间,也可以写成这样子:
for(int i=1;i<=n;i++)
for(int j=2;n/j>=i;j++) bz[i*j]=true;
- 最后
bz[i]=false则说明
i为素数。
埃氏筛法
- 我们发现一个数可能会被筛很多次,这样太浪费时间了。
- 如果只拿素数去筛它的倍数,这样是否可行呢?
- 答案是:YES。因为一个合数的因数必有至少一个数为素数。所以素数可以筛掉所有的合数。
- 代码:
for(int i=1;i<=n;i++)
if(!bz[i]){
for(int j=2;n/j>=i;j++)
bz[i*j]=true;
}
- 可以证明时间复杂度约为
O(nlog2log2n)。
- 这样一来
5e6如果用一开始的筛法会达到
1.1∗108级别,而埃氏筛法只会达到
2.2∗107级别,如果
n达到
6e6或
7e6级别,优化的效果会更明显。
欧拉筛法(线性筛)
- 看标题就知道时间复杂度…
- 如果一个合数的约数有多个素数,它仍会被筛很多次。
- 考虑优化到极致——一个合数只会被筛一次。
- 我们就让它只会被它的最小质因子给筛掉吧!
- 好像有点难…,先放代码:
for(int i=1;i<=n;i++){
if(!bz[i]) pri[++pri[0]]=i;
for(int j=1;j<=pri[0];j++){
if(n/pri[j]<i) break;
bz[i*pri[j]]=true;
if(i%pri[j]==0) break;
}
}
- 其中
pri表示的是素数的集合。
- 你看罢,大惊曰:“这二重循环怎么看都不像线性的啊!”
- 但有时候眼睛会出差错,我们需要严谨的证明。
- 首先要清楚我们的目的——每个数只会被它的最小质因子给筛掉。
- 欧拉筛最重要的一句话是:
if(i%pri[j]==0) break;
- 这是个啥东西呢?
- 我们考虑一个合数
x=
i∗j,其中
j为素数,我们要证明
i与
j的组合不会被break掉当且仅当
j为
x的最小质因子。
- 考虑反证法。
- 如果
j不是最小质因子,设
x的最小质因子为
k,则
imodk=0,因为有上面那条break语句的存在,所以我们第二重循环在枚举到素数
k时就会被break掉,根本不会枚举到
j。
- 因此一个数只会被筛一次,又因为这个二重循环每一次必然会筛掉一个数,所以时间复杂度是线性的,
O(n)。
- 悄悄告诉你:之所以看起来不想线性的,是因为它又常数!但不是特别大(2左右)。
- 不过如果你写的有没的话常数是可以缩小的。
总结