牛客挑战赛51-C NIT的数 #回文数#

牛客挑战赛51-C NIT的数 #回文数#

题目

给定一个数\(x\),求满足\(x\le y\),且\(y\)是回文数,这两个条件中的第\(k\)小的\(y\)

\[1\le x\le 10^{12},1\le k\le 10^8,答案严格小于10^{18} \]

如:

输入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\)(二分加上求位数),尽管不是最优解,但相关回文数的知识还是挺重要的

posted @ 2021-06-21 11:10  追梦人1024  阅读(65)  评论(0编辑  收藏  举报