題目描述
所謂孿生素數指的是間隔為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); } }
參考網址: