题目描述
难度分:1500
输入T(≤104)表示T组数据。
每组数据输入k(1≤k≤1012)。
输出第k个不含4的正整数,例如k=4时答案为5。
输入样例
7
3
5
22
10
100
12345
827264634912
输出样例
3
6
25
11
121
18937
2932285320890
算法
二分答案+数位DP
题目很明显是有单调性的,一个数x越大,显然区间[1,x]内不含4的数字就越多,因此可以利用单调性来确定最后一个满足[1,x]内不含4的数字x是多少。
而对于一个给定的上界x,就可以用数位DP
来计算[1,x]中不含4的数字个数。使用灵茶山艾府的记忆化搜索模板来做即可。dp[i][islimit][isnum]表示[0,i−1]数位已经填数完毕,贴合上界情况为islimit,之前是否真的填过数isnum,从数位i开始填到最后能得到多少个不含4的数。
复杂度分析
时间复杂度
根据状态定义,粗略估计状态一共有18×2×2个,状态转移时最多遍历0~9除4以外一共9个数,在长整型内进行二分大概需要60次左右,因此常数操作大约是18×22×9×60×104(事实上有很多较小的k,复杂度会比这个低很多)。
空间复杂度
开辟的dp数组就是额外空间消耗,是20×22的量级。
C++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
LL dp[20][2][2];
LL dfs(string& s, int i, int islimit, int isnum) {
if(i == s.size()) {
return isnum;
}
LL &v = dp[i][islimit][isnum];
if(v != -1) return v;
LL cnt = isnum? 0: dfs(s, i + 1, 0, 0);
int digit = s[i] - '0';
int ub = islimit? digit: 9;
for(int d = 1 - isnum; d <= ub; d++) {
if(d == 4) continue;
cnt += dfs(s, i + 1, islimit && d == digit, 1);
}
return v = cnt;
}
LL check(LL n) {
string s = to_string(n);
memset(dp, -1, sizeof(dp));
// 计算[1,n]中不含4的数字个数
return dfs(s, 0, 1, 0);
}
LL solve(LL k) {
LL left = 1, right = 1e15;
while(left < right) {
LL mid = left + right >> 1;
if(check(mid) >= k) {
right = mid;
}else {
left = mid + 1;
}
}
return right;
}
int main() {
int T;
scanf("%d", &T);
while(T--) {
LL k;
scanf("%lld", &k);
printf("%lld\n", solve(k));
}
return 0;
}