PAT Basic 1013
本题代码可以参考这里。
原题: 1013. 数素数 (20)
令Pi表示第i个素数。现任给两个正整数 M <= N <= 104,请输出PM到PN的所有素数。
输入格式
输入在一行中给出M和N,其间以空格分隔。
输出格式
输出从PM到PN的所有素数,每10个数字占1行,其间以空格分隔,但行末不得有多余空格。
输入样例
5 27
输出样例
11 13 17 19 23 29 31 37 41 43
47 53 59 61 67 71 73 79 83 89
97 101 103
注意
时间限制: 100 ms
内存限制: 65536 kB
代码长度限制: 8000 B
判题程序: Standard
作者: CHEN, Yue
题目分析
判断某个数是否为素数是一类很重要的题目。可以仅根据素数定义遍历判断,但容易超时。需要对算法进行优化,但又不能太复杂,否则临场容易出错。
PS:超时的原因 a).取余运算耗时;b).进行了大量取余运算。
-
基本优化
- 大于2的素数都是奇数
- 如果N没有[2, sqrt(N)]之间的素因子,那么N是素数
基于以上两点,可以节省很多不必要的判断时间。(偶数,(sqrt(N), N) 全部跳过)
int is_prime(int num){ int up_bound=0, divisor=1; if( 2<num && num%2){ up_bound = (int)sqrt(num); for(divisor=3; divisor<=up_bound; divisor+=2){ if( 0 == num % divisor ) return 0; } return 1; }else if(2 == num) return 1; else return 0; }
这个算法,C可以轻松通过时间限制。python就吃力了,PN较大的时候会超时。
-
筛表法
筛表法是一种用空间换时间的方法。这个方法不是说给定一个数,然后去取余啥的判断是否为素数,而是首先整理出一定范围内(比如1到1000之间)所有的素数,称为素数表,然后判断给定的数是否在素数表里面。
参考这篇博客
素数筛法是这样的: 1.开一个大的bool型数组prime[],大小就是n+1就可以了.先把所有的下标为奇数的标为true,下标为偶数的标为false. 2.然后: for( i=3; i<=sqrt(n); i+=2 ){ if(prime[i]) for( j=i+i; j<=n; j+=i ) prime[j]=false; } 3.最后输出bool数组中的值为true的单元的下标,就是所求的n以内的素数了。 原理很简单: 当i是质(素)数的时候,i的所有的倍数必然是合数。 如果i已经被判断不是质数了,那么再找到i后面的质数来把这个质数的倍数筛掉。
这个算法有一点需要注意,那就是素数表大小的选择。用
1
的方法跑一下程序,可以发现题目要求的第10000
个素数是104729
,比例大概是1/11。所以需要大致了解一下一定范围内,素数占自然数的比例。先看代码#define MAX 300001 /* ... */ bool prime_list[MAX] = {false, false, true}; for(i=3; i<MAX; i+=2){ prime_list[i] = true; prime_list[i+1] = false; } up_bound = (int)sqrt(MAX); for(i=3; i<=up_bound; i+=2){ if(prime_list[i]){ for( j=i+i; j<MAX; j+=i){ prime_list[j] = false; } } }
MAX不能太小,比如MAX=100001,那么就选不到第10000个素数了。对于C,MAX可以设为很大,比如MAX=300001,程序跑下来时间只有几个ms。
但是在用python实现这个算法时,如果选MAX=300001,那么每个测试样例都会超时。我的做法是MAX随输入变化,MAX=PN*10+5001,最大耗时76ms<100ms,按照10000/100000,再加10001大概多10%的时间,比例系数和常数项可以通过本地测试的结果确定,耗时达标即可。
对比下可以发现,C的时间效率大概是python的10~20倍,内存效率10倍。
-
其他方法
2和3非常容易理解与记忆,对于普通OJ已经够用。当然还有更高效但更复杂的方法,但是临场不一定会用,这里就不再班门弄斧了。
//另外有一个trick,先本地把一定范围内的素数全部打出来,也就是本地直接生成素数表,然后…
//不知道算不算作弊,紧急情况用。
部分测试用例
-
test1
输入 5 27 输出 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101 103
-
test2
输入 10000 10000 输出 104729
其他参考
-
[题目分析]—[第1节]—[基本优化]中两个前提条件的证明。
第一点很好证明。主要证第二点:【如果N没有[2, sqrt(N)]之间的素因子,那么N是素数】
首先根据定义,1不是素数。 其次,我们用反证法证明其逆否命题: 【若N是合数,那么N存在[2, sqrt(N)]之间的素因子】 证明: 设N=a×b, sq=sqrt(N) 根据算术基本定理,设a为素数,b是a以外,N的所有素因子之积 由反证法 i). N的每个素因子都大于sq. ii). a>sq ==> b>sq,a>sq ==> N = a×b > sq×sq > N 等式不成立,原题得证。故其逆否命题也是成立的,即 【如果N没有[2, sqrt(N)]之间的素因子,那么N是素数】
-
[题目分析]—[第2节]—[筛表法]原理的补充证明。
for( i=3; i<=sqrt(n); i+=2 ){ if(prime[i]) for( j=i+i; j<=n; j+=i ) prime[j]=false; }
-
为什么 i<=sqrt(N) 作为循环结束?(参考上面的证明)
-
为什么 i>sqrt(N) 后面没筛过的奇数都是素数?
证明: 若sqrt(N)后面的奇数为合数,则其存在 <=sqrt(N)的素因子p。 但内嵌的for循环 for( j=i+i; j<=n; j+=i ) prime[j]=false; 已经确保了含有p这个因子的合数都被标为false, 也就是 >sqrt(N)的数中合数(奇数+偶数)已经被筛掉了, 因此循环结束后, >sqrt(N)的奇数都是素数。
-
(END)