素数 - 九度教程第51题
题目:
时间限制:1 秒 内存限制:32 兆 特殊判题:否
题目描述:
输入一个整数 n(2<=n<=10000),要求输出所有从 1 到这个整数之间(不包括1和这个整数)个位为 1 的素数,如果没有则输出-1。
输入:
输入有多组数据。 每组一行,输入 n。
输出:
输出所有从1到这个整数之间(不包括1和这个整数)个位为1的素数(素数之间用空格隔开,最后一个素数后面没有空格),如果没有则输出-1。
样例输入:
100
样例输出:
11 31 41 61 71
来源:
2008 年北京航空航天大学计算机研究生机试真题
解析:
依次枚举每个数判断其是否为素数,复杂度过高,且整个过程显得粗暴而不具有技巧性。下面提出一种更好的办法解决该问题。
首先考虑这样一个命题:若一个数不是素数,则必存在一个小于它的素数为其因数。这个命题显然是正确的。假如已经获得了小于一个数的所有素数,只需确定该数不能被这些素数整除,这个数即为素数。但是这样依然需要大量的枚举测试工作。可以换一个角度,在获得一个素数时,即将它的所有倍数标记为非素数,这样当遍历到一个数时,它没有被任何小于它的素数标记为非素数,则确定其为素数。按照如下步骤完成工作:
从2开始遍历2到10000的所有整数,若当前整数没有因为它是某个小于其的素数的倍数而被标记成非素数,则判定其为素数,并标记它所有的倍数为非素数。然后继续遍历下一个数,直到遍历完2到10000区间内所有的整数。此时,所有没被标记成非素数的数字即为要求的素数。这种算法被称为素数筛法。
代码:
#include<stdio.h>
int prime[10000];//保存筛得的素数
int primeSize; //保存素数的个数
bool mark[10001];//若mark[x]为true,则表示该数x已被标记成非素数
void init() //素数筛法
{
for(int i=1;i<=10000;i++)
{
mark[i]=false;
} //初始化,所有数字均没被标记
primeSize=0;//得到的素数个数为0
for(int i=2;i<=10000;i++)//依此遍历2到10000所有数字
{
if(mark[i]==true) //若该数字已经被标记,则跳过
{
continue;
}
prime[primeSize++]=i;//否则,又新得到一个新素数
for(int j=i*i;j<=10000;j+=i)//并将该数的所有倍数均标记成非素数
{
mark[j]=true;
}
}
}
int main()
{
init(); //在程序一开始首先取得2到10000中所有素数
int n;
while(scanf("%d",&n)!=EOF)
{
bool isOutput=false; //表示是否输出了符合条件的数字
for(int i=0;i<primeSize;i++)//依此遍历得到的所有素数
{
if(prime[i]<n && prime[i]%10==1)//测试当前素数是否符合条件
{
if(isOutput==false)//若当前输出为第一个输出的数字,则标记已经输出了符合条件的数字,且该数字前不输出空格
{
isOutput=true;
printf("%d",prime[i]);
}
else
{
printf(" %d",prime[i]);//否则输出这个数字前输出一个空格
}
}
}
if(isOutput==false) //若始终不存在符合条件的数字
{
printf("-1\n"); //输出-1并换行
}
else
{
printf("\n"); //换行
}
}
return 0;
}
利用素数筛法,在处理输入的数字前,先处理出2到10000区间内所有素数。当输入n时,则依次比较已经得到的素数是否符合返回条件。若符合则输出,否则继续比较下一个素数。
筛法中使用了一个技巧。当判定i为素数,要标记其所有倍数为非素数时,并没有从2*i开始标记,而是直接从i*i开始标记。因为ik(k<i)已经在求k的某个素因数(可能是k本身,即k本身为素数,但该素因数必小于i)时被标记过了,即ik同时也是k的素因数的倍数。所以这里直接从i的平方开始标记起。尽可能避免重复工作也是程序优化的一大思路。
使用一个bool变量表示是否已经输出符合条件的数字。有两个目的,其一保证了除第一个输出的数字外,其他数字输出时均在其前附加一个空格,以达到题目要求的输出数字之间存在空格而最后一个数字后没有空格的要求。其二,作为判断依据使在不存在符合条件的素数时,按题目要求输出-1。
素数筛法通常作为程序真正开始处理输入数据前的预处理使用,即预先处理出相关区间内的所有素数,以备后续工作使用。