Tips:观看本篇题解之前,请确保你已经注册了Atcoder账号或注册了洛谷账号,以便查看题目
题目传送门
Atcoder 原题(出自Atcoder Beginner Contest 363)Atcoder Beginner Contest 363 D - Palindromic Numberhttps://atcoder.jp/contests/abc363/tasks/abc363_d
如果你没有Atcoder账号,也可以在洛谷查看题目
洛谷 ABC363 D - Palindromic Numberhttps://www.luogu.com.cn/problem/AT_abc363_d
声明
本题解仅代表个人思路,如有异议请及时指出,谢谢orz
1.题意描述
本题就是在求第 n 小的回文数,要注意第 1 小的回文数为 0.
2.整体思路
(1) 自定义说明
先确定回文串长度的一半 len(我们定义为回文长度),举个例子:
当 n 为 14 时,很显然此时第 14 小的回文串为 44 ,长度的一半为 11,那么 len=1;
特别地,当回文串长度 s 为奇数时,我们记该回文串的 len=(s+1)/2,方便我们后续求倒序的操作
当 n 为 46 时,此时第 46 小的回文串为 363 ,长度的一半为 2,那么 len=2.
(2) 构思过程
首先,我们可以从 1 开始枚举 i ,目标是寻找到第 n 小的回文串的回文长度 len ,在每次的枚举中,计算满足 len=i 时 n 的个数,即记录有 k 个回文串满足 len==i ,很显然 k 可以递推出来
递推公式:枚举 i 时,我们同时记录一个变量 s ,初始 s=0 ,在每次循环中都让 s+=1,当 s==2 时,说明距离上次 s==0 已经过去两次了,那么我们再记录一个变量 base ,方便下面的计算,用来表示回文串的前半部分为 base+c(c为常数且c<=base) , 此时应该把回文串的前半部分(也就是我们枚举的 base )的值乘上 10 ,容易证明回文串的前半部分(长度为len)在 s==0 和 s==1 时是一样的(有且仅有最后一位是否重复2次,那么分两种情况讨论:
当 s!=2 时:k[i]=k[i−1];
当 s==2 时:k[i]=k[i−1]∗10;
根据这两个递推式,我们即可计算出 k 数组的前缀和,k[j] 表示有多少个回文长度小于等于 j 的回文串,当 k[j]>=n 时,说明答案的 len 一定小于等于 j .
那么显然,k[j]−base 也就是答案的 len 的回文串第 p 个最小的 ,那么 第 n 个最小的应该是 n−(k[j]−base)
最后我们再根据 n 的 len 的奇偶性来输出后半部分的回文串即可
其实我们也可以不用记录 len,只需要根据 s 的取值来判断 len 的奇偶性即可
3.代码实现
示例代码
#include <bits/stdc++.h>
using namespace std;
unsigned long long n,k,s,tot,base;
int main()
{
cin >> n;
tot = 1;
base = 9;
while (1)
{
tot += base;
if (tot >= n)
{
tot -= base;
break;
}
s++;
if (s == 2)
{
s = 0;
base *= 10;
}
}
base /= 9;
k = n-tot+base-1;
cout<<k;
if (s == 0)
{
k /= 10;
}
while (k)
{
cout<<k%10;
k/=10;
}
return 0;
}