天天看點

<Sicily>1005. Prime Palindromes質數回文數的判斷方法

原題如下

1005. Prime Palindromes

Time Limit: 1sec Memory Limit:256MB

Description

The number 151 is a prime palindrome because it is both a prime number and a palindrome (it is the same number when read forward as backward). Write a program that finds all prime palindromes in the range of two supplied numbers a and b (5 <= a < b <= 100,000,000); both a and b are considered to be within the range .

簡譯:找出a到b之間既是回文數又是質數的數。

Input

There are multiple test cases.

Each case contains two integers, a and b.

a=b=0 indicates the end of input.

Output

For each test case, output the list of palindromic primes in numerical order, one per line.

Sample Input Copy

5 500
0 0           

複制

Sample Output Copy

5
7
11
101
131
151
181
191
313
353
373
383           

複制

思路

首先分成兩部分考慮。

回文數

怎麼找出a和b之間的回文數?一個個判斷?

有一個比較快的方法就是構造,因為根據回文數的性質,很容易構造出一定範圍内的回文數。比如,用12,可以構造出121和1221;234可以構造出23432和234432。到這裡我們大概可以看出規律了,一個n位數可以構造出2n-1或者2n位的回文數。

那從哪個數開始構造起呢?我們可以先計算下限a的位數digit,然後其(digit+1) / 2就是開始構造的數的最小位數,比如a是500,共有3位數,那麼就從2位數開始構造起,從10-99可以構造出505,606,999等比500大的回文數,如果還沒到達上限,就繼續用三位數即100-999構造,直到超出上限為止。

代碼如下:

void palindromesBetween(int a, int b){
	// 根據a的位數計算下限 
	int digit = 0; // a 的位數 
	int temp = a;
	while(temp > 0){
		temp = temp / 10;
		digit++;
	}
	int min = 1;
	for(int i = 1; i < (digit + 1) / 2; i++){
		min *= 10;
	}
	for(int low = min; low < b; low *= 10){ // 從最小的數開始構造 
		int res;
		for(int i = 0; i < 2; i++){ // 分兩種情況構造 
			for(int num = low; num < low * 10; num++){
				if(i == 0) res = num / 10; // 奇數位數 
				else if(i == 1) res = num; // 偶數位數 
				int n = num;
				while(n > 0){
					res = res * 10 + n % 10;
					n = n / 10;
				}
				if(res > b) break; // 超出上限,停止構造 
				if(res >= a)
					cout << res << endl;
			}
			if(res > b) break;
		}
		if(res > b) break;
	}
}           

複制

質數

一個簡單的判斷方法就是用數x除以2~x/2,如果都不能除盡,則說明x沒有除了1和本身之外的因數,則為質數。

但還有另外一個更快的方法,可以跳過很多沒必要判斷的數。原理是:一個大于等于5的質數一定可以表示為6n+1或6n+5,即除以6的餘數一定是1或5。很容易證明,因為顯然6n,6n+2,6n+3,6n+4都不是質數。

但形式為6n+1或6n+5的也不一定是質數。并且,如果x是一個質數,那麼一定有6n+1或6n+5的形式,是以必定是一個奇數,是以不可能被6n,6n+2,6n+4整除,另外如果x能被6n+3整除,那麼一定得是3的倍數,但是6n是3的倍數,是以6n+1和6n+5不可能是6n+3的倍數,即x不可能被6n+3整除。是以隻需要判斷該數能否被6n+1或6n+5整除即可,即每6個數隻需要判斷兩個數。

此外,其實并不用計算到x/2,因為一個數分解為兩個因數,一定有一個大于sqrt(x),一個小于sqrt(x),如果在sqrt(x)左側找不到因數,那右側肯定也找不到,是以隻需要判斷到sqrt(x)。

有了這些原理,就可以開始寫代碼了:

bool isPrime(int num){
	if(num == 2 || num == 3)
		return true; 
	if(num % 6 != 1 && num % 6 != 5){
		return false;
	}
	for(int i = 5; i <= (int)sqrt(num); i += 6){
		if(num % i == 0 || num % (i + 2) == 0){
			return false;
		}
	}
	return true;
}           

複制

合并優化

有了以上的方法,合并起來就可以進行題目的求解了。但其實還可以進行優化。

因為一個偶數長度的回文數,一定可以被11整除,是以不可能是質數。

原因是11的倍數有一個性質:奇數位上數字之和 = 偶數位上數字之和,逆過來也成立。而偶數長度的回文數一定滿足這個性質,因為對稱的數位一定一個在奇數位一個在偶數位。

是以其實沒必要生成偶數位的回文數,這樣可以減少很多計算。

再結合題目的數字範圍,整合後的代碼如下:

#include <iostream>
#include <math.h>

using namespace std;

bool isPrime(int);

int main(){
	int a, b;
	while(true){
		cin >> a >> b;
		if(b == 0) break;
		// 先輸出a到11的質數 
		for(int i = a; i <= 11; i++){
			if(isPrime(i)){
				cout << i << endl;
			}
		}
		// 根據a的位數計算下限 
		int digit = 0; // a 的位數 
		int temp = a;
		while(temp > 0){
			temp = temp / 10;
			digit++;
		}
		int min = 10;
		for(int i = 2; i < (digit + 1) / 2; i++){
			min *= 10;
		}
		for(int low = min; low < b; low *= 10){
			int res;
			for(int num = low; num < low * 10; num++){
				res = num / 10;
				int n = num;
				while(n > 0){
					res = res * 10 + n % 10;
					n = n / 10;
				}
				if(res > b) break;
				if(res >= a && isPrime(res))
					cout << res << endl;
			}
			if(res > b) break;
		}
	}
}

bool isPrime(int num){
	if(num % 6 != 1 && num % 6 != 5){
		return false;
	}
	for(int i = 5; i <= (int)sqrt(num); i += 6){
		if(num % i == 0 || num % (i + 2) == 0){
			return false;
		}
	}
	return true;
}           

複制

另外附上判斷一個數是否為回文數的方法:

思路是把這個數倒過來看是否跟原來相等。

bool isPalindrome(int num){
	int newNum = 0;
	int n = num;
	while(n > 0){
		int r = n % 10;
		n = n / 10;
		newNum = newNum * 10 + r;
	}
	return newNum == num;
}           

複制