牛客挑战赛51-C NIT的数 #回文数#
牛客挑战赛51-C NIT的数 #回文数#
题目
给定一个数\(x\),求满足\(x\le y\),且\(y\)是回文数,这两个条件中的第\(k\)小的\(y\)
如:
输入1
14 1
输出1
22
输入2
99 2
输出2
101
输入2
中,满足条件的\(y\)有99 101 111 121...
,第二小的是101
思路
做出这题,你只要一个OEIS
大概意思是:设\(a\)是一个回文数,\(a\)在回文序列{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55, 66, 77, 88, 99, 101, 111, 121, 131, 141, 151, 161, 171, 181, 191, 202, 212, 222, 232, 242, 252, 262, 272, 282, 292, 303, 313, 323, 333, 343, 353, 363, 373, 383, 393, 404, 414, 424, 434, 444, 454, 464, 474, 484, 494, 505, 515,...}
(注意包括0
)的位置为f(a)
举几个例子你就知道怎么求f(a)
了:
- \(a=123\ 321,f(a)=1\ 123\)
- \(a=12 21,f(a)=1\ 12\)
- \(a=123\ 21,f(a)=123+100=223\)
- \(a=98\ 9,f(a)=98+10=108\)
- \(a=1,f(a)=1+1=10\)
都能明白了吧,具体的证明可以这样想:123321
是由123
变出来的,而123
前面还有123
个数,可以变成11,22...122 221
和_ , 1_1 , 2_2 , 3_3, ... , 97_79 ,98_89 , 99_99
,前者与1,2,3,...,122
一一对应,共122
个数,后者与0,1,2,3,...99
一一对应,但是一个'_
'中可以填0~9
任何一个数,所以后者一共可以生成\(100\times 10=1000\)个回文数.所以123321
前面共有\(122+1000=1122\)个数,排名就是\(1\ 123\)
如果回文数的长度为奇数,也同理
根据排名,我们也可以反推出原回文数(如果原回文数的长度为偶数,排名的最高位一定是\(1\),次高位一定不为0,否则,排名的最高位可能是\(1\)~\(9\),当最高位为\(1\)时,次高位一定是\(0\))
回到题目,就显得简单多了,我们二分找出大于等于\(x\)的最小回文数的排名,排名加上\(k\)后找到对应的回文数输出即可
代码
AC
#include <iostream>
#include <cstdio>
#define ll long long
#define int long long
using namespace std;
int Getlen(ll x) {
int cnt = 0;
while(x != 0) {
++cnt;
x /= 10;
}
return cnt;
}
ll pow10[15];
ll GetRanking(ll x) {//求回文数的排名(还没有验证正确性,写完后发现没用)
ll ans;
int len = Getlen(x);
ans = x / pow10[len / 2] + pow10[len / 2];
return ans;
}
ll GetNum(ll rank) {//知道排名求对应的回文数
int len = Getlen(rank);
ll rpart = 0;
if(len == 1) return rank - 1 ;
if(rank / pow10[len - 1] == 1 && rank / pow10[len - 2] % 10 != 0) {
ll tmp = rank % pow10[len - 1];
while(tmp != 0) rpart = rpart * 10 + tmp % 10 , tmp /= 10;
return (rank - pow10[len - 1]) * pow10[len - 1] + rpart;
}
else if(rank / pow10[len - 1] == 1) {
ll tmp;
tmp = (rank - pow10[len - 2]) / 10 , rank -= pow10[len - 2];
while(tmp != 0) rpart = rpart * 10 + tmp % 10 , tmp /= 10;
return (ll)rank * pow10[len - 2] + rpart;
} else {
ll tmp;
tmp = (rank - pow10[len - 1]) / 10;
while(tmp != 0) rpart = rpart * 10 + tmp % 10 , tmp /= 10;
return (ll)(rank - pow10[len - 1]) * pow10[len - 1] + rpart;
}
}
signed main() {
pow10[0] = 1;
for(int i = 1 ; i <= 19 ; i++)
pow10[i] = pow10[i - 1] * 10;
ll x , k;
cin >> x >> k;
int rank;
ll l = 1 , r = (1ll << 30);
while(l < r) {//找大于等于x的最小回文数 的排名
ll mid = (l + r + 1) / 2;
ll num = GetNum(mid);
if(num == x) {
l = r = mid - 1;
break;
}
if(num < x)
l = mid;
else
r = mid - 1;
}
cout << GetNum(l + k);
return 0;
}
brute force
#include <iostream>
#include <cstdio>
#define int long long
using namespace std;
int read() {
int re = 0;
bool sig = 0;
char c = getchar();
while(c < '0' || c > '9') sig = (sig | (c == '-')) , c = getchar() ;
while(c >= '0' && c <= '9') re = (re << 1) + (re << 3) + c - '0' , c = getchar();
return sig ? -re : re;
}
int reverse(int x) {
int tmp = 0;
while(x != 0) {
tmp = tmp * 10 + x % 10;
x /= 10;
}
return tmp;
}
signed main() {
int x = read() , k = read();
for(int i = x ; true ; i++) {
if(reverse(i) == i) {
k--;
if(k == 0) {
cout << i;
break;
}
}
}
return 0;
}
随机数据
#include <bits/stdc++.h>
#define ll long long
using namespace std;
int random(int r , int l = 1) {
return (rand() % 1000 + rand() % 1000 * 1000 + rand() % 1000 * 1000000) % (r - l + 1) + l;
}
int main() {
map <ll , bool> h;
unsigned seed;
cin >> seed;
srand(seed * time(0));
printf("%d %d\n" , random(1000) , random(10000));
return 0;
}
最后
这个做法的复杂度大概是\(\log^2\)(二分加上求位数),尽管不是最优解,但相关回文数的知识还是挺重要的