一般筛法求素数
标签: PAT
下面是一个模板代码,在PAT考试中能够流畅默写会大大加速我们的编程速度,一般筛法虽然在速度上不如线性筛法,但是在PAT考试中,这一点性能可以忽略不计了.另外需要特别注意的是:
PAT打表解法有可能无法提交!
PAT打表解法有可能无法提交!
PAT打表解法有可能无法提交!
重要的事情说三遍!,一定要记住
在这样的情况下,熟练掌握筛法就是至关重要的了.
下面是没有优化的代码,
bool prime [MAX];
memset(prime,true,MAX);
vector<long> v;
for(int i = 2; i < MAX; i ++){
if(prime[i] == true){
v.push_back(i);
for(int j = i ; j < MAX ; j += i){
prime[j] = false;
}
}
}
有必要作几点说明:
- prime最好使用bool数组,bool虽然逻辑上是占用一个字节.但是实际上,在定义成一个bool数组后,编译器有可能会对这个数组做一些优化,使用一个字节的二进制位来代表true或者false.所以在一些数字比较大,对内存的限制比较紧张的情况下,使用bool数组可以有效的节省内存.
- memset函数的使用,为了避免因为平台不一样导致奇怪的问题,这里统一引入
memory.h
头文件. - 为了后面输出等其他操作的方便,存放素数的容器这里统一使用vector.
下面是一些可以优化的地方
- 内层循环中,j 从 i开始筛后面的数字,这样其实是完全不必要的.可以直接让j 从 i的平方开始往后筛.这样做的理由是i 到 i平方之间会路过的数字肯定已经被比i小的数字筛过了.例如:质数5如果按照常规方法,要经过
5
,10
,15
,20
,25
.将这些数字拆分就会发现依次是1 * 5
,2 * 5
,3 * 5
,4 * 5
,5 * 5
,而1
,2
,3
,4
与5
的乘积在之前肯定已经被筛过.所以可以直接让j = i * i开始内层循环.- 上面这种优化仍然是存在问题的.这会让j的定义必须变得特别大.因为当i = 10000的时候,j就必须要达到100000000才可以容纳,这回导致没有某种类型可以容纳它.所以我们让i的初始值定义为
sqrt(double(MAX) + 0.5)
,对MAX四舍五入后取根号操作,就可以让筛法的范围正好覆盖MAX的范围.
即使这样优化仍然有需要注意的地方
就是将素数放入vector的操作的位置.因为检查prime[i]是否为true的操作现在只进行到根号MAX
就停止了.所以原来地方的对V的操作无法访问所有为true的prime元素.所以在执行完筛法之后,我们需要再扫描一遍prime数组,将prime为true的元素单独放入.
典型例题
- PAT 1013 数素数:利用素数个数定理x/ln(x)估算出达到目标的素数范围,使用筛法晒出素数表.即可求解,注意控制格式.
- PAT 1027 素数对擦想:筛出固定范围的表后,遍历减去相邻的即可求得个数.
下面是最终代码
bool prime [MAX];
memset(prime,true,MAX);
vector<long> v;
int m = sqrt(double(MAX) + 0.5);
for(int i = 2; i < m; i ++){
if(prime[i] == true){
for(int j = i * i ; j < MAX ; j += i){
prime[j] = false;
}
}
}
for(long i = 2 ; i < MAX;i ++){
if(prime[i])
v.push_back(i);
}