在o(n)的时间内求得n以内的质数
如果一个数是质数,那么他的倍数一定是合数,从而从1到n筛选一次即可,但是有一个问题,对于数10来说,在质数是2的时候被筛掉过一次,质数是5的时候又被筛掉一次,造成了冗余,如何解决这个问题呢?
在枚举的时候,我们只枚举i的质数倍。比如2i,3i,5i,...,而不去枚举4i,6i, 此外,在从小到大依次枚举质数p来计算i的倍数时,我们还需要检查i是否能够整除p。若i能够整除p,则停止枚举。
利用该算法,可以保证每个合数只会被枚举到一次。我们可以证明如下命题:
假设一个合数k=M*p1,p1为其最小的质因子。则k只会在i=M,primeList[j]=p1时被筛掉一次。
首先会在i=M,primeList[j]=p1时被筛掉是显然的。因为p1是k的最小质因子,所以i=M的所有质因子也≥p1。于是j循环在枚举到primeList[j]=p1前不会break,从而一定会在i=M,primeList[j]=p1时被筛掉
其次不会在其他时候被筛掉。否则假设k在i=N, primeList[j]=p1时被筛掉了,此时有k=N*p2。由于p1是k最小的质因子,所以p2 > p1,M > N且p|N。则i=N,j枚举到primeList[j]=p1时(没到primeList[j]=p2)就break了。所以不会有其他时候筛掉k。
同时,不枚举合数倍数的原因也在此:对于一个合数k=M *2*3。只有在枚举到i=M*3时,才会计算到k。若我们枚举合数倍数,则可能会在i=M时,通过M*6计算到k,这样也就造成了多次重复计算了。
代码如下: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
using namespace std;
int N;
const int MAXN = 1e6+10;
bool isPrime[MAXN];
int prime[MAXN];
int Eula()
{
memset(isPrime, 1, sizeof(isPrime));
memset(prime, 0, sizeof(prime));
int count = 0;
for( int i = 2 ; i <= N ; i++ )
{
if(isPrime[i])
{
prime[count++] = i;
}
for( int j = 0 ; j < count ; j++ )
{
if(prime[j] * i > N) break;
isPrime[prime[j] * i] = false;
if(i % prime[j] == 0) break;
}
}
return count;
}
int main(int argc, char *argv[])
{
scanf("%d", &N);
printf("%d\n", Eula());
}