数论二·Eular质数筛法

#1295 : 数论二·Eular质数筛法

时间限制:10000ms
单点时限:1000ms
内存限制:256MB

描述

小Ho:小Hi,上次我学会了如何检测一个数是否是质数。于是我又有了一个新的问题,我如何去快速得求解[1,N]这个区间内素数的个数呢?

小Hi:你自己有什么想法么?

小Ho:有!我一开始的想法是,自然我们已经知道了如何快速判定一个数是否是质数,那么我就直接将[1,N]之间每一个数判定一次,就可以得到结果。但我发现这个方法太笨了。

小Hi:确实呢,虽然我们已经通过快速素数检测将每一次判定的时间复杂度降低,但是N个数字的话,总的时间复杂度依旧很高。

小Ho:是的,所以后来我改变了我的算法。我发现如果一个数p是质数的话,那么它的倍数一定都是质数。所以我建立了一个布尔类型的数组 isPrime,初始化都为true。我从2开始枚举,当我找到一个isPrime[p]仍然为true时,可以确定p一定是一个质数。接着我再将N以内 所有p的倍数全部设定为isPrime[p*i]=false。

写成伪代码为:

isPrime[] = true
primeCount = 0
For i = 2 .. N
	If isPrime[i] Then
		primeCount = primeCount + 1
		multiple = 2
		While (i * multiple ≤ N)
			isPrime[i * multiple] = false
			multiple = multiple + 1
		End While 
	End If
End For
  

小Hi:小Ho你用的这个算法叫做Eratosthenes筛法,是一种非常古老的质数筛选算法。其时间复杂度为O(n log log n)。但是这个算法有一个冗余的地方:比如合数10,在枚举2的时候我们判定了一次,在枚举5的时候我们又判定了一次。因此使得其时间复杂度比O(n)要 高。

小Ho:那有没有什么办法可以避免啊?

小Hi:当然有了,一个改进的方法叫做Eular筛法,其时间复杂度是O(n)的。

输入

第1行:1个正整数n,表示数字的个数,2≤n≤1,000,000。

输出

第1行:1个整数,表示从1到n中质数的个数

样例输入
9
样例输出
4

开个bool数组f记录i是否为素数。
先全部赋为1,表示这个数为素数。
从2开始筛合数,若i为素数,则a*i,a∈z肯定为合数。
对于每一个i,都去筛一遍,只筛素数倍,不筛合数倍,因为每个合数倍的都会被其他的比i大的数筛掉。
然后还有一个优化,假设枚举到的素数为p,当p|i时就可以break了。
这样可以保证每一个合数k都是被k/它那个最小的质因数筛掉的,证明如下:

假设一个合数k=M*p1p1为其最小的质因子。则k只会在i=MprimeList[j]=p1时被筛掉一次。

首先会在i=MprimeList[j]=p1时被筛掉是显然的。因为p1k的最小质因子,所以i=M的所有质因子也≥p1。于是j循环在枚举到primeList[j]=p1前不会break,从而一定会在i=MprimeList[j]=p1时被筛掉

其次不会在其他时候被筛掉。假设ki=N, primeList[j]=p2时被筛掉了,此时有k=N*p2

由于p1k最小的质因子,所以p2 > p1M > Np1|N(因为p2|M,N/p1==M/p2)。则i=Nj枚举到primeList[j]=p1(没到primeList[j]=p2)break了。所以不会有其他时候筛掉k

 1 #include<set>
 2 #include<map>
 3 #include<queue>
 4 #include<stack>
 5 #include<ctime>
 6 #include<cmath>
 7 #include<string>
 8 #include<vector>
 9 #include<cstdio>
10 #include<cstdlib>
11 #include<cstring>
12 #include<iostream>
13 #include<algorithm>
14 #define maxn 1000010
15 using namespace std;
16 bool f[maxn];
17 int su[maxn];
18 int main()
19 {
20   freopen("!.in","r",stdin);
21   freopen("!.out","w",stdout);
22   int n,num=0;
23   scanf("%d",&n);
24   for(int i=1;i<=n;i++) f[i]=1;
25   for(int i=2;i<=n;i++){
26     if(f[i]==1) su[++num]=i;
27     for(int j=1;j<=num;j++){
28       if(i*su[j]>n)break;
29       f[i*su[j]]=0;
30       if(i%su[j]==0) break;
31     }
32   }
33   printf("%d",num);
34   return 0;
35 }

 


posted @ 2017-03-27 22:43  嘘丶  阅读(210)  评论(0编辑  收藏  举报