洛谷:P1217回文质数,JAVA

题目描述

因为 151 既是一个质数又是一个回文数(从左到右和从右到左是看一样的),所以 151151 是回文质数。

写一个程序来找出范围 [a,b](5≤a<b≤100,000,000)(一亿)间的所有回文质数。

输入格式

第一行输入两个正整数 ab

输出格式

输出一个回文质数的列表,一行一个。

输入输出样例

输入 #1复制

5 500

输出 #1复制

5

7

11

101

131

151

181

191

313

353

373

383

说明/提示

Hint 1: Generate the palindromes and see if they are prime.

提示 1: 找出所有的回文数再判断它们是不是质数(素数).

Hint 2: Generate palindromes by combining digits properly. You might need more than one of the loops like below.

提示 2: 要产生正确的回文数,你可能需要几个像下面这样的循环。

题目翻译来自NOCOW。

USACO Training Section 1.5

产生长度为 5 的回文数:

for (d1 = 1; d1 <= 9; d1+=2) {    // 只有奇数才会是素数for (d2 = 0; d2 <= 9; d2++) {
         for (d3 = 0; d3 <= 9; d3++) {
           palindrome = 10000*d1 + 1000*d2 +100*d3 + 10*d2 + d1;//(处理回文数...)
         }
     }
 }

思路:

回文可以通过将数字倒过来然后跟原来的数比较是否相等来判断

判断是否为回文是很快的,这道题的主要瓶颈是质数的判断,因为一不小心就会TLE或者MLE了

质数的判断有三种方法:(以下是我的个人理解,如有错误欢迎纠正)

  1. 暴力枚举

让数字n除以小于等于Math.sqrt(n)的所有正数(我们用变量i遍历),如果除尽了,说明n不是质数,return false;如果i能够走出循环,则n是质数,return true;

时间复杂度为O(nlogn)

  1. 埃式筛

筛掉所有质数的倍数,会有重复筛的数字。

时间复杂度为O(nloglogn)

  1. 线性筛

是埃式筛的优化,线性筛不会有重复筛的数字。

线性筛的实质是筛掉一个数和当前已知的所有质数的乘积。

任何数字都可以写成一个其因数中的最小质数prime[j]乘以一个数i的形式,比如18=2*9=3*6=2*3*3。为了方便理解,我把它写成公式的形式:

n = prime[j] * i

其中,
n是我们要筛掉的数。
prime[j]是n的所有因数中最小质数。
i是n的所有因数中除它本身外的最大数字。

比如我们要筛掉99,那么n就是99,99=1*99=3*33=9*11,可知99的因数有1,3,9,11,33,99,其中最小的质数是3,所以prime[j]=3, 而33是99的所有因数除它本身外的最大数字,故i=33.

下面是代码原理:

我们通过最小质数去筛掉某个数字,既然是用其因数中的最小质数prime[j]去筛,那么i一定是n的所有因数中出它本身外的最大数字,比如n还是99,我们用其因数中的最小质数3去筛99,那就是当i=33时我们才把99筛掉,而不是在i加到9时,i加到11时,i没有加到33时,就把99筛掉,所以也可以说通过某个数的因子中除它本身外最大的因子去筛某个数字。

可以结合线性筛和以上去理解。

时间复杂度:O(n)

代码:

注意:由题知,回文质数一定是奇数,所以偶数就不用考虑了,for循环时i+=2

方法一:

使用暴力枚举筛选质数

package 循环结构;

import java.util.Scanner;

public class P1217o {

    public static boolean palindromes(int n) {
        int a = n,b = 0;
        while(a != 0) {
            b = b * 10 + a % 10;
            a /= 10;
        }
        if(b == n) {
            return true;
        }
        return false;
    }
    
    public static boolean prime(int n) {
        for(double i = 2; i <= Math.sqrt(n); i ++) {
            if(n%i == 0.0)
                return false;
        }
        return true;
    }
    
    
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Scanner sc = new Scanner(System.in);
        int a = sc.nextInt();
        int b = sc.nextInt();
        if(a%2 == 0) //判断a是否为偶数,若为偶数则++
            a++;     //a变成奇数
        for(int i = a; i <= b; i+=2) { //由题知,回文质数都是奇数
            if(palindromes(i) == true && prime(i) == true)
                    System.out.println(i);
        }
    }
}

方法二:

使用埃式筛来筛质数。

只使用埃式筛会TLE,因为埃式筛会筛出一个质数表,即它会将<=b的所有质数筛出来,也就是说它会把<=b的所有数字全部筛一遍,所以仅仅通过埃式筛是无法AC的,需要观察一些条件(我是没观察出来)。

题解中大神说偶数位必然不是回文质数(除了11),理由是

如果一个回文素数的位数是偶数,则它的奇数位上的数字和与偶数位上的数字和必然相等。
根据数的整除性理论,容易判断这样的数肯定能被11整除,所以它就不可能是素数。

数论里的知识,看不懂,所以就记住这个结论吧。

加上这个条件就不会TLE了

package 循环结构;

import java.util.Arrays;
import java.util.Scanner;

public class P1217improve {

//    对每个数首先判断位数,但还是TLE了
//    public static boolean weishu(int n) {
//        if((n!=11 && n >= 10 && n <= 100) ||(n >= 1000 && n <= 1000) || (n >= 100000 && n <= 1000000) || (n >= 10000000)) {
//            return false;
//        }
//        return true;
//    }
    
    public static boolean palindromes(int n) {
        int a = n,b = 0;
        while(a != 0) {
            b = b * 10 + a % 10;
            a /= 10;
        }
        if(b == n) {
            return true;
        }
        return false;
    }
    
    public static void prime(boolean is_prime[],int b) {
        for(int i = 2; i <= b; i++) {
            if(is_prime[i] == true) {
                for(int j = 2; i*j <= b; j ++) {
                    is_prime[i*j] = false;
                }
            }
        }        
    }
    
    
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Scanner sc = new Scanner(System.in);
        int a = sc.nextInt();
        int b = sc.nextInt();
        //10000000~99999999都是偶数位的,100000000也不是回文质数,所以b>=10000000
        //居然AC了
        //仔细想想确实,因为每个数首先判断位数的方法还是需要遍历所有数,这个直接省掉了10000000后的数。
        if(b >= 10000000)
            b = 9999999;
        boolean[] is_prime = new boolean[b+1];
        Arrays.fill(is_prime, true);
        is_prime[0] = is_prime[1] = false;
        prime(is_prime, b);
        if(a%2 == 0)
            a ++;
        for(int i = a; i <= b; i+=2) {
//           if(weishu(i) == true && palindromes(i) == true && is_prime[i] == true)
            if( palindromes(i) == true && is_prime[i] == true)
                    System.out.println(i);
        }
    }
}

方法三:

使用线性筛来筛选质数。

线性筛必须要有两个数组,比埃式筛需要的内存更多,埃式筛一不小心都会MLE更别说线性筛了...

当然,加上判断位数的条件也可以AC。

package 循环结构;

import java.util.Arrays;
import java.util.Scanner;

public class P1217im {

    public static boolean palindromes(int n) {
        int a = n,b = 0;
        while(a != 0) {
            b = b * 10 + a % 10;
            a /= 10;
        }
        if(b == n) {
            return true;
        }
        return false;
    }
    
    public static void prime(boolean is_prime[],int prime[], int n) {
        int cnt = 0;
        for(int i = 1; i <= n; i++) {
            if(is_prime[i] == true) {
                prime[++cnt] = i; 
            }
            for(int j = 1; j <= cnt && i*prime[j] <= n; j ++) {
                //System.out.printf("%d*prime[%d]=%d\n",i,j, i*prime[j]);
                is_prime[i*prime[j]] = false;
                if(i % prime[j] == 0)
                    break;
                //如何理解假如i % prime[j] == 0就break?
                //假如i=k*prime[j],如果没有这句,那么j++,
                //某合数 = i*prime[j+1] = (k*prime[j])*prime[j+1] = (k*prime[j+1])*prime[j],
                //这样的话,这个合数就是用prime[j+1]筛掉的,而不是用最小质数筛掉的,
                //加上这句,就保证了所有数都是被它的最小质数筛掉的.
                //比如18,当i=6时,prime[6*2]=false,接着运行有两种情况:
                //1.假如没有break,j++,prime[6*3]=false,18就是用3筛掉的;
                //2.假如break,i++,当i加到9,prime[9*2]=false,18就是用2筛掉的;
                //因为prime总是从下标1开始,也就是每次循环都是质数从小到大开始筛的,
                //当i能够除尽prime[j],说明某合数的最小质数有可能是prime[j],
                //因为prime[j+1]>prime[j],所以就一定不是prime[j+1]。
                //某合数 = i * prime[j+1] = (k*prime[j])*prime[j+1] 
                //假设k=n*prime[j-1],(n>0)
                //那么某合数 = i*prime[j+1] 
                //          = (k*prime[j])*prime[j+1]
                //          = (n*prime[j-1]*prime[j])*prime[j+1]
                //          = (n*prime[j+1]*prime[j])*prime[j-1]
                //令i' = n*prime[j+1]*prime[j] 
                //那么某合数 = i'*prime[j-1]
                //那么某合数的最小质数就可能是prime[j-1]
                //假设n = m*prime[j-2] ,(m>0), 同理
                //.......   
                //一直到m=1为止,1取余任何大于1的正整数都不等于0吧
                //所以回到i % prime[j] == 0,总结一下:
                //只要i能够除尽prime[j],就要break。
                //因为i%prime[j]==0说明某合数的最小质数可能是prime[j],一定不是prime[j+n] (n>=1) 
            }
        }
    }        
    
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Scanner sc = new Scanner(System.in);
        int a = sc.nextInt();
        int b = sc.nextInt();
        if(b >= 10000000)
            b = 9999999;
        boolean[] is_prime = new boolean[b+1];
        int[] prime = new int[b+1];
        Arrays.fill(is_prime, true);
        Arrays.fill(prime, 0);
        is_prime[0] = is_prime[1] = false;
        prime(is_prime, prime, b);
        if(a%2 == 0)
            a++;
        for(int i = a; i <= b; i+=2) {
            if(palindromes(i) == true && is_prime[i] == true)
                    System.out.println(i);
        }
    }
}

总结:

  • 回文可以通过将数字倒过来然后跟原来的数比较是否相等来判断

  • 筛选质数有三种方法:

  • 暴力枚举

  • 埃氏筛法

  • 线性筛

  • 偶数位必然不是回文质数(除了11)

你可能感兴趣的:(洛谷JAVA练习,java,算法)