状态表示:\(f_{i,j}\) 表示由 \(i\) 个数组成的数,左边有连续 \(j\)(\(0\le j\le 3\))个 \(6\)。
边界:\(f_{0,0}=1\)
转移方程:
\(f_{i,0}=9\times(\sum_{j=0}^2f_{i-1,j})\)(可以在至多连续 \(2\) 个 \(6\) 前填 \(0,1,\dots,5,7,\dots,9\))
\(f_{i,1}=f_{i-1,0}\)(从连续 \(0\) 个 \(6\) 前补一个 \(6\))
\(f_{i,2}=f_{i-1,1}\)(从连续 \(1\) 个 \(6\) 前补一个 \(6\))
\(f_{i,3}=10\times f_{i-1,3}+f_{i-1,2}\)(从连续 \(3\) 个 \(6\) 转移或从连续 \(2\) 个 \(6\) 前补一个\(6\))
dp 完成后,我们先通过 \(f_{i,3}\) 确定第 \(x\) 小的魔鬼数的位数,然后按照“试填法”的思想,从左到右依次考虑每个数位,同时记录当前末尾已经有连续几个 \(6\)。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll f[30][4];
int T,m,x;
int main() {scanf("%d",&T);f[0][0]=1;for(int i=1; i<=20; i++) {f[i][0]=((f[i-1][0]+f[i-1][1]+f[i-1][2])<<3)+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]+(f[i-1][3]<<3)+(f[i-1][3]<<1);}while(T--) {scanf("%d",&x);for(m=3; f[m][3]<x; m++);for(int i=m,k=0; i>=1; i--) {for(int j=0; j<=9; j++) {ll cnt=f[i-1][3];if(j==6||k==3) for(int l=max(3-k-(j==6),0); l<3; l++) cnt+=f[i-1][l];if(cnt<x) x-=cnt;else {if(k<3) {if(j==6) k++;else k=0;}printf("%d",j);break;}}}printf("\n");}return 0;
}