题目描述:
给定一个正整数n,请你求出1~n中质数的个数。
输入格式
共一行,包含整数n。
输出格式
共一行,包含一个整数,表示1~n中质数的个数。
数据范围
1≤n≤10^6
输入样例:
8
输出样例:
4
分析:
方法一:埃式筛法
从2到n遍历所有的数,如果i是质数,就将i不超过n的倍数全部标记为合数。
内层循环会执行n / i次,也就是说,一共会执行n / 2 + n / 3 + ... + n / n = n * (1 / 2 + 1 / 3 + ... 1 / n) = O(nlogn),由调和级数的时间复杂度是lnn级别可推出上式。实际上,只有n为素数时,内层循环才会被执行,而由质数定理知,1到n中素数大概有n / lnn个,如果只是单纯的考虑质数占比是1 / lnn的话,得到的结论是该算法的时间复杂度是线性的。实际上,只是计算仅包含素数项的调和级数的和复杂度是loglogn的,因此,埃式筛法真实的时间复杂度是O(nloglogn),在n不是特别大时,该算法是接近于线性的。
#include <iostream>
using namespace std;
const int maxn = 1000005;
bool st[maxn];
int main(){
int n,res = 0;
cin>>n;
for(int i = 2;i <= n;i++){
if(st[i]) continue;
res++;
for(int j = i;j <= n;j += i) st[j] = true;
}
cout<<res<<endl;
return 0;
}
方法二:线性筛法
埃式筛法是将每个质数的倍数都筛一遍,比如6就会被2和3重复筛,这是没有必要的,我们可以采取线性筛法,规定每个数只会被其最小的质因子筛掉。
#include <iostream>
using namespace std;
const int maxn = 1000005;
bool st[maxn];
int primes[maxn];
int main(){
int n,res = 0;
cin>>n;
for(int i = 2;i <= n;i++){
if(!st[i]) primes[res++] = i;
for(int j = 0;primes[j] <= n / i;j++){
st[i * primes[j]] = true;
if(!(i % primes[j])) break;
}
}
cout<<res<<endl;
return 0;
}
可能上面的代码不易理解,不妨看下程序执行时筛掉的数。
分析上面的结果,i为质数时,会筛掉i和不超过它的质数乘积的数,比如2筛掉了4,3筛掉了6和9;i为合数时,会筛掉不超过i乘以不超过其最小质因子的质数乘积的数,比如4筛掉了8,6筛掉了12。那么每个质数的倍数是在何时被筛掉的呢?4是i = 2时筛掉的,6是i = 3时,乘上了素数表的最小质因子2筛掉的,8是i = 4时筛掉的,也就是说如果n是某质数x的倍数,则会在i = max(x,n / x)时被筛掉。对于质数情况,i = 2只会筛掉4,不会乘以大于它的质数3筛掉6,因此6只会在i = 3时被筛掉;对于合数情况,一旦i乘到了其最小质因子,就不再往上筛了。换而言之,与其说每个数只会被其最小质因子筛掉,不如说每个数只会被其最大因子筛掉。i是质数,则i的倍数中不小于i平方的数的最大因子一定是i;i是合数,则i的倍数中不小于i的k(k是i的最小质因子)倍的数的最大因子一定是i,比如12,最小质因子是2,不超过12的倍数24可以被筛掉,但是36的最大因子是18而不是12,所以此时还不能筛掉。
具体的说法,对于每个数x,p为其最小质因子,则p和x / p中的一个是x的最大因子,一个是x的最小质因子,当然,x为质数时,x的最大因子是等于最小质因子的,所以,被i的最大因子筛掉等同于被i的最小质因子筛掉。2可以筛掉n / 2个数,3可以筛掉n / 3 - n / 6个数,5筛掉n / 5个数...,最后总的复杂度是O(n)。