http://hihocoder.com/contest/hiho93/problem/1
小Hi:我们可以知道,任意一个正整数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筛法的优势也就越明显。
输入
第1行:1个正整数n,表示数字的个数,2≤n≤1,000,000。
输出
第1行:1个整数,表示从1到n中质数的个数
9
4
#include <stdio.h>
#include <string.h>
#include <math.h>
int flag[10006];
int num[10000],total,now,n;
void solve(int index,int Mul,int K)
{
int i,t;
if(K==0)
{
now+=n/Mul;
return;
}
for(i=index;i<total-K+1;i++)
{
Mul*=num[i];
t=now;
if(Mul<=n)
{
solve(i+1,Mul,K-1);
}
if(t==now)//优化剪枝(1)
return;
Mul/=num[i];
}
}
int Judge(int x)
{
int t,i;
t=sqrt(x*1.0);
for(i=2;i<=t;i++)
if(n%i==0)
return 0;
return 1;
}
int main()
{
int i,j,t,Count;
memset(flag,0,sizeof(flag));
for(i=2;i<=10000;i++)
{
if(flag[i])
continue;
for(j=2;j*i<=10000;j++)
flag[i*j]=1;
}
scanf("%d",&n);
n++;
{
Count=0;
if(n<=10000)
{
for(i=2;i<n;i++)
if(!flag[i])
Count++;
}
else
{
t=sqrt(1.0*n);
total=0;
for(i=2;i<=t;i++)
if(!flag[i])
num[total++]=i;
for(i=1;i<=total;i++)
{
now=0;
solve(0,1,i);
if(now==0)
break;
if(i&1)
Count+=now;
else
Count-=now;
}
Count-=total;
Count=n-Count-1;
if(Judge(n))
Count--;
}
printf("%d\n",Count);
}
return 0;
}
或者:
#include <iostream>
#define N 1000000
using namespace std;
bool isPrime[N+10];
int main(){
int n;
cin >> n;
for(int i=2; i<=n; i++)
isPrime[i] = true;
for(int d=2; d*d <=n; d++){
if(isPrime[d]){
for(int k=d*d; k<=n; k += d)
isPrime[k] = false;
}
}
int cnt = 0;
for(int i=2; i<=n; i++)
if(isPrime[i]) cnt++;
cout << cnt << endl;
return 0;
}
#include <stdio.h>
#include <string.h>
#define Max 1000000
int isPrime[Max+5];
int primeList[Max+5];
int Eular(int N)
{
int i,j,primeCount=0;
memset(isPrime,1,sizeof(isPrime));
for(i=2;i<=N;i++)
{
if(isPrime[i])//往素数表中添加新的质数i
{
primeCount++;
primeList[primeCount]=i;
}
for(j=1;j<=primeCount;j++)//扫描质数表
{
if(i*primeList[j]>N)
break;
isPrime[i*primeList[j]]=0;//枚举i的质数倍
if(i%primeList[j]==0)
break;
}
}
return primeCount;
}
int main()
{
int n;
scanf("%d",&n);
printf("%d\n",Eular(n));
return 0;
}