数论二·Eular质数筛法

如何去快速得求解[1,N]这个区间内素数的个数呢?

自然我们已经知道了如何快速判定一个数是否是质数,那么我就直接将[1,N]之间每一个数判定一次,就可以得到结果。

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

发现如果一个数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
  

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

一个改进的方法叫做Eular筛法,其时间复杂度是O(n)的。

输入

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

输出

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

样例输入
9
样例输出
4
解法提示:Eular质数筛选法

我们可以知道,任意一个正整数k,若k≥2,则k可以表示成若干个质数相乘的形式。Eratosthenes筛法中,在枚举k的每一个质因子时,我们都计算了一次k,从而造成了冗余。因此在改进算法中,只利用k的最小质因子去计算一次k。

首先让我们了解一下Eular筛法,其伪代码为:

isPrime[] = true
primeList = []
primeCount = 0
For i = 2 .. N
	If isPrime[i] Then
		primeCount = primeCount + 1
		primeList[ primeCount ] = i
	End If 
	For j = 1 .. primeCount
		If (i * primeList[j] > N) Then
			Break
		End If
		isPrime[ i * primeList[j] ] = false
		If (i % primeList[j] == 0) Then
			Break
		End If
	End If
End For
	

与Eratosthenes筛法不同的是,对于外层枚举i,无论i是质数,还是是合数,我们都会用i的倍数去筛。但在枚举的时候,我们只枚举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,这样也就造成了多次重复计算了。

综上,Eular筛法可以保证每个合数只会被枚举到一次,时间复杂度为O(n)。当N越大时,其相对于Eratosthenes筛法的优势也就越明显。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define LL long long

using namespace std;

//FILE *stream;
int n;
const int N = 1000005;
bool isPrime[N];
int primeList[N];
int primeCount = 0;
int Count[N];

void Eular()
{
	memset(isPrime,true,sizeof(isPrime));
	for (int i = 2; i < N; ++i)
	{
		if (isPrime[i])
		{
			primeCount += 1;
			primeList[primeCount] = i;
		}
		Count[i] = primeCount;
		for (int j = 1; j <= primeCount; ++j)
		{
			if (i*primeList[j] >= N)
				break;
			isPrime[i*primeList[j]] = false;
			if (i % primeList[j] == 0)
				break;
		}
		
		/*
		int multiple = 2;
		while (i*multiple <= N)
		{
			isPrime[i*multiple] = false;
			multiple += 1;
		}
		*/
	}
}

int main()
{
	//freopen_s(&stream, "in.txt", "r", stdin);
	Eular();
	//cout << "input:" << endl;
	while (cin >> n)
		cout << Count[n] << endl;

	//freopen_s(&stream, "CON", "r", stdin);
	//system("pause");
	return 0;
}



你可能感兴趣的:(常用简单算法)