引子:
我现在想知道1——1e8的范围内有多少个素数(质数),有什么方法?
朴素法,对于每一个数n,我们判断它是否是素数。
像这样:
bool check(int curr)
{
int i;
for(i = 2; i < sqrt(curr)+1; ++i)
{
if(curr % i == 0) return false;
}
return true;
}
对于每一个数,我们最多需要sqrt(n)次便可以判定出它是素数还是合数。
太慢了!这个时间复杂度已经可以飞出天际了。
我们需要一种快速的方法筛取素数。
我们观察素数和合数最大的不同——合数有多个因数(除了1与自身),而素数不行。那么当我们得知对于int x,2 * x ,3 * x……便一定都是合数。
看来这个效率很高,但是还是不够快。因为会出现判断重复。
那么我们便使用欧拉筛法来筛取素数。
前文的方法做了什么无用功?例如20的判断,我们在2,4,5,10的时候都去判定了一次,而欧拉筛法可以保证20 只被判定1次。
如何保证?
先放出源代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 10000005;
int n, x[maxn], pri[maxn/10], tot, curr, num;//m以内的质数不会m/10个,不放心 || m很小的话可以再开大一点
int main()
{
//freopen("test.in", "r", stdin);
scanf("%d%d", &n, &num);
x[1] = 1;
for(int i = 2; i != n+1; ++i)
{
if(!x[i])
{
pri[tot++] = i; //如果一个数是合数那么它一定会在之前被判定出来,故剩下的一定是质数
}
for(int j = 0; j != tot; ++j)//当前的所有质数
{
curr = pri[j]*i;//一定是合数
if(curr > n) break;//超出范围
x[curr] = 1;//置为合数
if(i % pri[j] == 0) break;//重点
}
}
fclose(stdin);
return 0;
}
重点代码:
if(i % pri[j] == 0) break;
要说保证某数不被重复判断,关键就在这行代码上。
满足上式时,i是pri[j]的倍数,那么对于后面的pri[k]来说,i * pri[k]就可以被分解为pri[j] * (i / pri[j] *pri[k]),而该数在之前i == pri[j]的时候已经出现过,于是就重复了。直接退出,避免了重复判定。
欧拉筛法固然快,但是也有适用条件,例如求(n, n + 10)中的素数个数的时候,它就明显慢了。由于必须从1开始,所以欧拉筛法的用处在于直接打表而不是求某一段区间。对于这种问题,朴素法未尝不可。
欧拉筛法的原理分析至此结束。
箜瑟_qi 2017.04.10 00:43