只要某数字的十进制表示中有三个6相邻,则该数字为魔鬼数,求第X小的魔鬼数(Xle 5e7)
这一类题目可以先用DP进行预处理,再基于拼凑思想,用“试填法"求出最终的答案
(F[i,3])表示由 (i) 位数字构成的魔鬼数有多少个,(F[i,j](0le jle 2)) 表示由 (i) 位数字构成的,开头已经有连续 (j) 个6的非魔鬼数有多少个。(允许前导0的存在,想一想为什么)
转移方程
- (F[i,0] = 9*(F[i-1,0] + F[i-1,1] + F[i-1,2]))
- (F[i,1] = F[i-1,0])
- (F[i,2] = F[i-1,1])
- (F[i,3] = F[i-1,2] + 10 * F[i-1,3])
然后一位一位的试填,要注意前面填过的数字结尾如果有 k 个6,通过后面拼接 3-k 个6也可以构成魔鬼数
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
ll f[21][4];
int T,n,l;
void init(){
f[0][0] = 1;
for(int i=1;i<=20;i++){
f[i][0] = 9*(f[i-1][0] + f[i-1][1] + f[i-1][2]);
f[i][1] = f[i-1][0];
f[i][2] = f[i-1][1];
f[i][3] = f[i-1][2] + 10 * f[i-1][3];
}
}
int main(){
init();
scanf("%d",&T);
while(T--){
scanf("%d",&n);
//l为答案的长度
for(l=3;f[l][3] < n;l++);
//k表示填过的数字末尾有k个6
for(int i=l,k=0;i;i--){
for(int j=0;j<=9;j++){
ll cnt = f[i-1][3];//后面预处理出的魔鬼数
//找能够拼凑出来的魔鬼数
if(j == 6 || k == 3){
if(k == 3){
for(int x = 0;x < 3;x++)
cnt += f[i-1][x];
}else{
for(int x = max(3-k-1, 0);x<3;x++){
cnt += f[i-1][x];
}
}
}
if(cnt < n) n -= cnt;
else{
if(k < 3) j == 6 ? k ++ : k=0;
printf("%d",j);break;
}
}
}
cout<<endl;
}
return 0;
}