先把左半边镜像成回文;若还不够大,就给中间位置进位,再重新镜像,得到严格大于原数的最小回文数。
OJ: luogu
题目 ID: P1609
难度:普及/提高-
标签:字符串模拟高精度
日期: 2026-06-20 11:53
题意
给定一个十进制整数 N,要求找到一个最小的回文数 P,满足:
P > N
由于 N 最长可以有 100 位,所以不能用普通整数类型直接处理,应该把它当成字符串来做。
思路
先看一个最直接的小数据暴力:
cpp
#include <bits/stdc++.h>
using namespace std;
string s;
bool is_palindrome(const string &str) {
int len = str.size();
for (int i = 0; i < len / 2; i++) {
if (str[i] != str[len - 1 - i]) {
return false;
}
}
return true;
}
// 字符串形式加一,适合小数据暴力。
void add_one(string &str) {
int pos = str.size() - 1;
while (pos >= 0 && str[pos] == '9') {
str[pos] = '0';
pos--;
}
if (pos >= 0) {
str[pos]++;
} else {
str = "1" + str;
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> s;
// brute.cpp:不断把数字加一,直到遇到下一个回文数。
// 这个做法很直观,但只适合小数据验证。
do {
add_one(s);
} while (!is_palindrome(s));
cout << s << '\n';
return 0;
}brute.cpp 的想法很直白:把原数不断加一,直到遇到第一个回文数。
这个做法虽然正确,但如果下一个回文数离当前数字很远,就会做很多次无用加法。
关键想法:答案的左半边基本已经确定
如果我们想构造“最小的、并且大于 N 的回文数”,那么最自然的做法就是:
- 尽量保留高位
- 只在必要的时候才把中间往上调
于是先把原串的左半边直接复制到右半边,得到一个回文串。
例如:
12345 -> 1232180801 -> 80808
这个回文串有两种情况:
情况 1:镜像后已经大于原数
如果镜像后的回文串已经满足 > N,那它一定就是最小答案。
原因很简单:
- 它的前半部分没有变大
- 又已经是回文
- 再想更小,就只能改动更高位,这反而更不可能保持
> N且更小
情况 2:镜像后不够大
如果镜像后:
- 小于原数
- 或者等于原数
那就说明光靠“直接镜像”不够,必须把中间部分进位。
做法是:
- 如果长度是奇数,就从正中间那一位开始进位
- 如果长度是偶数,就从左中位开始进位
把它加一后,再重新把左边镜像到右边。
例如:
-
12932- 先镜像得到
12921,不够大 - 中间进位后变成
13021
- 先镜像得到
-
1991- 先镜像还是
1991 - 左中位进位后得到
2002
- 先镜像还是
特殊情况:全是 9
如果原数形如:
999999
那么答案一定是:
111011001
也就是:
1 + 若干个 0 + 1
这个情况单独处理最简单。
代码
cpp
#include <bits/stdc++.h>
using namespace std;
string s, t;
bool all_nine(const string &str) {
for (int i = 0; i < (int)str.size(); i++) {
if (str[i] != '9') {
return false;
}
}
return true;
}
// 把左半边镜像到右半边,生成一个回文串。
void make_palindrome(string &str) {
int len = str.size();
for (int i = 0; i < len / 2; i++) {
str[len - 1 - i] = str[i];
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> s;
// 全是 9 时,答案一定是 100...001。
if (all_nine(s)) {
cout << '1';
for (int i = 1; i < (int)s.size(); i++) {
cout << '0';
}
cout << '1' << '\n';
return 0;
}
t = s;
make_palindrome(t);
// 直接镜像后已经比原数大,就是最小答案。
if (t > s) {
cout << t << '\n';
return 0;
}
// 否则需要把中间这一位(或左中位)往前进位,
// 再重新镜像一次。
int pos = ((int)t.size() - 1) / 2;
while (pos >= 0 && t[pos] == '9') {
t[pos] = '0';
pos--;
}
t[pos]++;
make_palindrome(t);
cout << t << '\n';
return 0;
}复杂度
- 时间复杂度:
- 空间复杂度:
其中 len 是数字串长度。
总结
这题最核心的思路是:
- 先直接镜像左半边
- 如果还不够大,再给中间做一次进位
- 然后重新镜像
它本质上是一道字符串构造题,不需要真的做高精度大整数运算。