[USACO1.5] 回文质数 Prime Palindromes

利用偶数位回文除 11 外都不是质数的性质,只构造少量奇数位回文再试除判素。

OJ: luogu

题目 ID: P1217

难度:普及/提高-

标签:数论枚举构造python

日期: 2026-06-18 22:15

题意

给出区间 [a,b],输出其中所有既是回文数、又是质数的整数,每个数单独一行。

思路

如果从 ab 逐个扫描,再分别判断回文和质数,区间最大可以到 10^8,Python 和 C++ 都会做很多无用检查。

更好的方向是:不要在区间中寻找回文数,而是直接构造回文数。

一个关键性质是:

  • 除了 11 以外,所有偶数位回文数都能被 11 整除,因此不可能是质数。

所以需要检查的候选只剩:

  1. 一位数回文;
  2. 特判 11
  3. 奇数位回文。

奇数位回文可以由一个“前半部分种子”构造出来:

种子 构造结果
12 121
305 30503
9999 9999999

对种子字符串 s,构造式是:

text
s + s[-2::-1]

也就是保留最后一位作为中心,再把中心左边的部分反向接到右侧。

最后按从小到大的种子枚举回文数,落在 [a,b] 中时再用试除法判断质数即可。

Python 知识

  • /home/rainboy/mycode/hugo-blog/content/program_language/python/input_output_and_strings.md:用 map(int, input().split()) 读取一行两个整数。
  • /home/rainboy/mycode/hugo-blog/content/program_language/python/math_tools.md:用 math.isqrt 做精确整数平方根,质数试除时不依赖浮点数。
  • text[-2::-1] 是切片写法:从倒数第二个字符开始,向左反向取到开头。
  • 把答案先存成字符串列表,最后 "\n".join(answer) 输出,适合这种多行结果。

代码

python
from math import isqrt


def is_prime(x):
    if x < 2:
        return False
    if x == 2:
        return True
    if x % 2 == 0:
        return False

    for d in range(3, isqrt(x) + 1, 2):
        if x % d == 0:
            return False
    return True


def make_odd_palindrome(seed):
    text = str(seed)
    return int(text + text[-2::-1])


a, b = map(int, input().split())

answer = []

for x in range(1, 10):
    if a <= x <= b and is_prime(x):
        answer.append(str(x))

if a <= 11 <= b:
    answer.append("11")

for seed in range(10, 10000):
    value = make_odd_palindrome(seed)
    if value > b:
        break
    if value >= a and is_prime(value):
        answer.append(str(value))

print("\n".join(answer))

Guide 风格代码

cppbook《C++ 快速入门》教学风格的写法(std:: 前缀、i += 1 循环、0 起始下标):

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-08-14 15:18
 * update_at: 2026-08-14 15:18
 */
/* P1217 回文质数:组合「回文判断」和「质数判断」两个函数,只枚举少量回文候选。 */

#include <iostream>

// 试除法判断 x 是否为质数
bool is_prime(int x) {
    if (x < 2) {
        return false;
    }
    if (x == 2) {
        return true;
    }
    if (x % 2 == 0) {
        return false;  // 除 2 以外的偶数都不是质数
    }
    for (int d = 3; 1LL * d * d <= x; d += 2) {
        if (x % d == 0) {
            return false;
        }
    }
    return true;
}

// 判断 x 正着读和倒着读是否相同
bool is_palindrome(int x) {
    int reversed = 0;
    int rest = x;
    while (rest > 0) {
        reversed = reversed * 10 + rest % 10;  // 把 rest 的最低位搬到 reversed 前面
        rest /= 10;
    }
    return reversed == x;
}

// 由种子 x 构造奇数位回文数:12 -> 121,305 -> 30503
int make_odd_palindrome(int x) {
    int result = x;
    x /= 10;  // 最低位作为回文的中心,不再参与镜像
    while (x > 0) {
        result = result * 10 + x % 10;
        x /= 10;
    }
    return result;
}

int main() {
    int a, b;
    std::cin >> a >> b;

    // 一位数回文
    for (int x = 1; x <= 9; x += 1) {
        if (a <= x && x <= b && is_prime(x)) {
            std::cout << x << '\n';
        }
    }

    // 唯一可能是质数的偶数位回文数是 11
    if (a <= 11 && 11 <= b && is_prime(11)) {
        std::cout << 11 << '\n';
    }

    // 由种子 10..9999 构造 3、5、7、9 位回文数,按升序出现
    for (int seed = 10; seed <= 9999; seed += 1) {
        int palindrome = make_odd_palindrome(seed);
        if (palindrome > b) {
            break;  // 种子越大构造结果越大,后面的都不在区间内
        }
        if (palindrome < a) {
            continue;
        }
        // 构造保证是回文,这里组合两个判断函数作为校验
        if (is_palindrome(palindrome) && is_prime(palindrome)) {
            std::cout << palindrome << '\n';
        }
    }

    return 0;
}

Pythonic 写法

回文 + 质数:

python
import math
def is_prime(x):
    return x>1 and all(x%d for d in range(2,int(math.isqrt(x))+1))
def is_pal(x):
    s=str(x); return s==s[::-1]
a,b=map(int,input().split())
# odd digits only except 11 for large ranges optimization optional
for x in range(a,b+1):
    if is_pal(x) and is_prime(x):
        print(x)

复杂度

最多枚举 109999 的种子,构造出的奇数位回文覆盖到 10^8 以内。每个候选数用试除法判断质数,单次复杂度是 O(x)O(\sqrt x)。空间复杂度是 O(c)O(c),其中 c 是输出答案个数。

总结

这题的关键不是把判素数写得很快,而是先把候选数量降下来。看出偶数位回文数的整除性质后,只构造奇数位回文,就能避免扫描整个大区间。