题目描述
所谓孪生素数指的是间隔为2的相邻的素数,他们之间的距离已经近得不能再近了,就像孪生兄弟一样,最小的孪生素数是(3,5),在100以内还有(5,7),(11,13),(17,19),(17,19),(29,31),(41,43),(59,61),(71,73),总计8组。
但随着数字的增大,孪生素数的分布越来越稀疏,寻找起来也变得困难,那会不会在超过某个界限之后就再也没有孪生素数了呢?
孪生素数有无穷多个!这个猜想称为孪生素数猜想,但至今没有被严格证明,但借助计算机我们已经确实可以找到了任意大范围内的所有孪生素数对。
接下来你的任务就是计算不大于n的范围内的孪生素数对的个数!
解答要求时间限制:60000ms, 内存限制:64MB
输入
输入包含多组测试,每组测试占一行,包含一个整数n(1<n<100000001),输入到文件末尾结束。
输出
输出孪生素数的对数。
样例
输入样例 1 复制
10
100
输出样例 1
2
8
分析
看似简单的题,往往坑会很多,时间复杂度、空间占用大小都有限制,下面的解题思路很值得学习。
(来源于网络)
算法总体思路,因为题目有时间及空间要求,计算素数如果采用遍除法会超时,所以采用筛法求素数,
算法思路:创建一个大小为100000000的int型数组,第i个位置表示i是不是素数,初始化全部为0,开始排除不是素数的数,从2开始将2的所有倍数对应的数组位置置为1,表示其不是素数,
再从数组上取下一个没有被排除的数,将其所有倍数对应位置置为1,以此类推,直到取到的下一个数大于10000,此时在100000000的范围内已经没有合数了,数组初始处理完毕,
有了素数表,然后按输入的n累加个数即可
// 空间优化思路,因为大小为100000000的int型数组已经超过了题目要求的64M的空间限制,而我们每个int其实只有取值0和1,
所以为了节省空间,我们可以采用位操作,将一个int作为32位二级制数来操作,可以将空间使用率缩小32倍,此时此算法已经可以满足题目要求的时间及空间限制
// 除此以外,大于2的偶数很明显并不是素数,我们可以默认只处理奇数,同理3的倍数也不是素数,我们也可以在将它们排除在外,
排除这两个数的倍数不予处理之后,我们的数组大小可以继续缩小1/3(约4M左右),处理的数据量也减少到原来的1/3.
// 排除2和3倍数的办法:2和3的倍数以6为周期的周期性分布,比如6-11之间,不是2和3倍数的只有7(6+1)和11(6+5),12-17之间,只有13(6*2+1)和(6*2+5),
只有6*n+1和6*n+5的数不能被2或者3整除,才可能为素数,因此,我们把需要处理的数据初始化为2,3,5,7,11,13,17,19,23,25...,
此时,已经不用关心2和3的倍数,从5开始处理,将5*5,5*7,5*11...设置为非素数,然后是7*7,7*11,7*13...
// 由于排除了2和3的倍数,数组位置跟数字之间已经不是一一对应的,增加位置与数字之间的转换方法计算一下即可
java代码实现
import java.util.BitSet; import java.util.Scanner; //孪生素数 public class twinsPrime { static final int MAX=100000000; static BitSet bs = new BitSet(MAX/3+2); public static void main(String[] args) { // TODO Auto-generated method stub Scanner in = new Scanner(System.in); init(); while(in.hasNextInt()){ int n = in.nextInt(); System.out.println(count(n)); } } //统计n内的孪生素数的对数 public static int count(int n){ int count=1; if(n<5||n>MAX){ return 0; } for(int i=2;i<bs.size();i+=2){ if(getNumber(i+1)<=n && checkBit(i) && checkBit(i+1)){ count++; } } return count; } //初始化孪生素数 public static void init(){ int number; for(int i = 2;i<bs.size();i=getNearlyPosition(number+2,true)){ number = getNumber(i); if(number > Math.sqrt(MAX)){ return; } // 从后往前处理,避免从前往后处理被覆盖 int lastIndex = getNearlyPosition(getNumber(MAX/3+2)/number,false); for(int j = lastIndex;j>=i;j--){ while(!checkBit(j)){ j--; } int position = getPosition(number*getNumber(j)); if(position != 0){ changeBit(position); } } } } //获取离指定数字最近的未排除数字的索引 public static int getNearlyPosition(int number,boolean isAdd){ int position; while(number <= MAX){ position = getPosition(number); if(position != -1 && checkBit(position)){ return position; }else if(isAdd){ switch(number%6){ case 1:number+=4;break; case 5:number+=2;break; case 0:number+=1;break; case 2:number+=3;break; case 3:number+=2;break; case 4:number+=1;break; } }else{ switch(number%6){ case 1:number-=2;break; case 5:number-=4;break; case 0:number-=1;break; case 2:number-=1;break; case 3:number-=2;break; case 4:number-=3;break; } } } return 0; } //获取指定位置的数字 private static int getNumber(int index) { if(index > 1){ return index*3-1-(index&1); }else if(index == 1){ return 3; }else if(index == 0){ return 2; } return 1; } //求指定数字所在位置 private static int getPosition(int number){ int r = number%6; if(number>=4){ if(r==1||r==5){ return 2*(number/6)+(r==1?1:2); } return -1; }else if(number==2){ return 0; }else if(number==3){ return 1; }else{ return -1; } } /* *对指定位进行置1操作 *即不是素数 */ public static void changeBit(int index){ if(index>=bs.size() || index<0){ return; } bs.set(index); } /* *判断指定位是0还是1,0返回true,1返回false *检查指定位置是否是素数 */ public static boolean checkBit(int index){ if(index>bs.size() || index<0){ return false; } return !bs.get(index); } }
参考网址: