题解:P10958 启示录

状态表示:\(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;
}
posted @ 2024-09-16 11:34  cly312  阅读(5)  评论(0)    收藏  举报