一.题目链接:
POJ-3208
二.题目大意:
定义魔鬼数为含有连续 666 的数.
求第 k 大的魔鬼数.
三.分析:
很明显用填坑法解数位 DP.
假设当前枚举到数字的第 i 位且第 i 位为 j ,那我们需要知道小于等于当前枚举的数的魔鬼数有多少个.
显然需要预处理出 f[i][j].
f[i][0]: 不超过 i 位数且从第 i 位开始有 0 个连续的 6 的非魔鬼数的数的个数.
f[i][1]: 不超过 i 位数且从第 i 位开始有 1 个连续的 6 的非魔鬼数的数的个数.
f[i][2]: 不超过 i 位数且从第 i 位开始有 2 个连续的 6 的非魔鬼数的数的个数.
f[i][3]: 不超过 i 位数的魔鬼数的数的个数.
易得状态转移方程为:
f[i][0] = (f[i - 1][0] + f[i - 1][1] + f[i - 1][2]) * 9
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] * 10
详见代码.
四.代码实现:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
ll f[25][4];
void init()
{
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]) * 9;
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] * 10;
}
}
int main()
{
init();
int T;
scanf("%d", &T);
while(T--)
{
int n, m;
scanf("%d", &n);
for(m = 1; f[m][3] < n; ++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(0, 3 - k - (j == 6)); l < 3; ++l)
{
cnt += f[i - 1][l];
}
}
if(cnt < n) n -= cnt;
else
{
if(k < 3)
{
if(j == 6) ++k;
else k = 0;
}
printf("%d", j);
break;
}
}
}
printf("\n");
}
return 0;
}