题目:http://poj.org/problem?id=3208
数位DP,首先按位数预处理出每一种位数的情况,包括有多少个魔鬼数和有多少个以6开头的非魔鬼数,以便递推、累加等等;
然后先找出第X个魔鬼数的位数,再一位一位从0开始填数;
写法有些技巧,详见代码及注释。
代码如下:
#include<iostream> #include<cstdio> #include<cstring> using namespace std; int t,x,f[25][4]; void cl() { /* f[i][3]=f[i-1][3]*10+f[i-1][2]; f[i][2]=f[i-1][1]; f[i][1]=f[i-1][0]; f[i][0]=(f[i-1][0]+f[i-1][1]+f[i-1][2])*9; */ f[0][0]=1;//! for(int i=0;i<20;i++) { for(int j=0;j<=2;j++) { f[i+1][j+1]+=f[i][j]; f[i+1][0]+=f[i][j]*9; } f[i+1][3]+=10*f[i][3]; } } int main() { scanf("%d",&t); cl(); while(t--) { scanf("%d",&x); int m;//! for(m=3;f[m][3]<x;m++);//位数 ——实际总比f记录的多 for(int i=m,k=0;i>=1;i--)//所有魔鬼数数量累计 ,k表示末尾已经有k个6 for(int j=0;j<=9;j++) { long long cnt=f[i-1][3]; if(j==6||k==3)//另外加 for(int l=max(0,3-k-(j==6));l<3;l++)//l为加上的最小限制 cnt+=f[i-1][l]; if(cnt<x)//则第i位应填更大的数; { x-=cnt;//j+1,减去上一层的魔鬼数数量 continue; } else//本位填j,再缩小范围 { if(k<3&&j==6)k++; if(k<3&&j!=6)k=0;//k=3意为本身已经是魔鬼数,不再改变 printf("%d",j);//第i位填了j break; } } printf(" "); } return 0; }