情境
大家应该都知道用nlogn的时间复杂度筛出[1,n]的所有素数,但是当n的范围较大时,这个方法就不奏效了
今天我们谈谈素数的线性筛法,也就是用On的时间复杂度筛出[1,n]的所有素数
解析
nlog的算法之所以会慢,是因为它进行了很多重复的操作
比如,24可能就被筛了很多遍
那么我们优化复杂度的思路就是尝试避免这些重复操作
也就是让每个合数只被筛一次
怎么做呢?
我们让每个合数只会被自己的最小质因数筛到
先看一下代码:
代码
void solve(){for(int i=2;i<=n;i++){if(v[i]==0){//还没被筛到就是素数了prime[++tot]=i;v[i]=i;}for(int j=1;j<=tot;j++){ll now=prime[j];if(now>v[i]||now*i>n) break;//now*i>n就是超出范围了,显然也不要了v[now*i]=now;}}return;
}
其中:
v[i]数组表示i的最小质因数
prime是素数表
代码是怎么做的?
枚举每一个数,再枚举已有的素数,只要当前素数还不大于自己的最小质因数,就一直用它筛
这个算法可以说是:
巧夺天工
为什么这样是对的呢?
接下来我们来证明一下这么做为什么就能不重不漏的筛出所有素数
证明
我们要证明两部分:不重、不漏
不漏
会不会少筛一些合数?
假设有一个合数a,那么它一定有一个不等于它本身的最小质因数
也就是说,存在:
a=v*k(v为质数且v<k)
显然v不会大于k的最小质因数,否则k的那个最小质因数就也是a的最小质因数了
那么我们在代码中循环枚举到k时,显然就会用质数表中已经有的v把a筛出来
不重
考虑反证法
假设代码过程中存在i与素数now,使得:
now*i=a;
且a的最小的质因数比i还小
不妨还设这个最小质因数为v
由于now已经是素数,所以这个v一定是i的一个因数
也就是说i存在一个质因数且小于now
那么now就一定比i的最小质因数大了
就会在前面的判断中break出去
证毕