poj3208
题面:
古代人认为666是属于魔鬼的数。
不但如此,只要某数字的十进制表示中有三个连续的6,古代人也认为这是个魔鬼的数,比如666,1666,6663,16666,6660666等等。
古代典籍中经常用“第X小的魔鬼的数”来指代这些数,这给研究人员带来了极大的不便。
现在请编写一个程序,可以实现输入X,输出对应的魔鬼数。
输入格式
第一行包含整数T,表示共有T组测试数据。
每组测试数据占一行,包含一个整数X。
输出格式
每组测试数据占一行,输出一个魔鬼数。
数据范围
1≤T≤10001≤T≤1000,
1≤X≤5∗1071≤X≤5∗107
输入样例:
3
2
3
187
输出样例:
1666 2666 66666
设f[i][3]表示由i位数字构成的魔鬼数有多少,f[i][j](0<=j<=2)表示由i位数字构成的,开头已经由连续j个6的非魔鬼数有多少个。在计算f的时候,允许前导0的存在。
题解:
考虑第i位(最高位)是什么数字,容易得到转移方程
f[i][0]=9*(f[i-1][0]+f[i-1][1]+f[i-1][2])//但i位前面无连续的6时,i-1位前有几个都可以,第i位可以填除6以外的任何数
f[i][1]=f[i-1][0]//第i位填6
f[i][2]=f[i-1][1]//第i位填6
f[i][3]=f[i-1][2](第i位填6)+10*f[i-1][3](第i位填任何数)
然后通过“试填法“从最高位一位一位确定具体的数,同时记录当前末尾已经有连续的k个6(可以与开头由3-k的非魔鬼数构成新的魔鬼数统计进去)
从小到大枚举当前位填什么数字,通过预处理的F数组可以直接计算出后边的数位有多少种填法可以得到魔鬼数,与x比较即可
代码:
1 #include<iostream> 2 #include<cstring> 3 #include<cmath> 4 using namespace std; 5 long long f[21][4]; 6 int t,n,m; 7 void pre_work() 8 { 9 f[0][0]=1; 10 for(int i=0;i<20;i++) 11 { 12 for(int j=0;j<3;j++) 13 { 14 f[i+1][j+1]+=f[i][j]; 15 f[i+1][0]+=9*f[i][j]; 16 } 17 f[i+1][3]+=f[i][3]*10; 18 } 19 } 20 int main() 21 { 22 pre_work();//预处理 23 scanf("%d",&t); 24 //cin>>t; 25 while(t--) 26 { 27 //printf("%lld\n",f[3][3]); 28 scanf("%d",&n); 29 for(m=3;f[m][3]<n;m++);//确定位数 30 for(int i=m,k=0;i;i--) 31 { 32 for(int j=0;j<=9;j++) 33 { 34 long long cnt=f[i-1][3];//i-1位魔鬼数 35 if(j==6||k==3)//求由前l(0<=l<=2)个6开头的非魔鬼数与已经出现的3-l个6构成的魔鬼数个数 36 for(int l=max(3-k-(j==6),0);l<3;l++) 37 cnt+=f[i-1][l]; 38 if(cnt<n) 39 n-=cnt; 40 else 41 { 42 if(k<3)//如果已经出现了连续的3个6均可构成魔鬼数 43 { 44 if(j==6)k++; 45 else 46 k=0; 47 } 48 printf("%d",j); 49 break; 50 } 51 } 52 } 53 printf("\n"); 54 } 55 return 0; 56 }