题解 - 启示录
古代人认为 666 是属于魔鬼的数。
不但如此,只要某数字的十进制表示中有三个连续的 6 ,古代人也认为这是个魔鬼的数,比如 666,1666,6663,16666,6660666 等等。
古代典籍中经常用“第 X 小的魔鬼的数”来指代这些数,这给研究人员带来了极大的不便。
现在请编写一个程序,可以实现输入 X(\(X \leq 5\times 10^7\)),输出对应的魔鬼数。
思路
直接找是难的,那么先确定其位数。(我也不知道为啥)
设 \(f_{i, 3}\) 代表由 \(i\) 位数字构成的魔鬼数个数(不以 \(666\) 开头也可),\(f_{i, j}(0 \leq j \leq 2)\) 表示由 \(i\) 位数字构成、开头已有连续 \(j\) 个 \(6\) 的非魔鬼数的个数。
考虑第 \(i\) 位(最高位)会新加进来什么数字,不容易得到状态转移方程:
\(9\) 代表 \(0, 1, 2, 3, 4, 5, 7, 8, 9\)(因为这里 \(j = 0\),所以不能有 \(6\)。)分别加在后面的每个数的最高位。
代表把 \(6\) 加到后面的数的最高位。
代表把 \(6\) 加到后面的数的最高位。
代表把 \(6\) 加到后面的数的最高位,和把任意数(包括 \(0\))加到后面的数的最高位(因为这里加入了 \(0\),所以相当于把之前的都加上了,相当于前缀和)。
既然确定了每个位都有共几个魔鬼数,那么就可以确定第 X 个魔鬼数的位数,然后试填,从左向右依次尝试填入每个数字。
当 k 小于 \(3\),其意为当前数位前 k 个都是 \(6\),当 k 大于等于 \(3\),不再改变,表示该数已经是魔鬼数。
该数位为 j
,设当前数位后共有 cnt
个符合条件的数。
cnt
初始等于 f[i-1][3]
(后者符合则当前一定符合)。
如果 j6(可以与后方拼接)或 k3(当前数已经是魔鬼数)则可以加入认为当前还有当前数位及以前已经有 (j6)+k 个 6,还需要 max(0, 3-k-(j6)) 个 6。
如果 cnt 小于 \(n\),代表当前数不够大,将 n 减去 cnt(更大的数一定包含当前的 cnt。)。
否则证明当前数已经够大,先修改 k,再输出 j。
点击查看代码
#include <bits/stdc++.h>
namespace {
#define fiin(x) freopen(x".in", "r", stdin)
#define fiout(x) freopen(x".out", "w", stdout)
#define files(x) fiin(x), fiout(x)
using namespace std;
#define ll long long
#define db double
const int man = 1e6+10;
}
int T, n;
ll f[22][5];
int main () {
#ifndef ONLINE_JUDGE
files("test");
#endif
f[0][0] = 1; // 0?
for (int i = 1; i < 20; ++ i) {
f[i][0] = 9*(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]+10*f[i-1][3];
} scanf("%d", &T);
while (T --) {
scanf("%d", &n);
int m = 2;
while (f[++ m][3] < n) ;
for (int i = m, k = 0; i; -- i) { // try on no.i, there are k*"6"(unbroken) in the front.
for (int j = 0; j <= 9; ++ j) { // no.i's number is j
ll cnt = f[i-1][3]; // there is cnt kinds of methods can make the number be monster number.
if (j==6 || k==3) // k==3 means it's monster number now.
// // j==6 means it can plus f[i-1][2](6+66…)
// // if k == 3,it can plus any,or it can just plus f[i-1][2].
for (int x = max(0, 3-k-(j==6)); x <= 2; ++ x) cnt += f[i-1][x];
// printf("-%d %d %lld-\n", x, i-1, f[i-1][x]);
// }
// printf("%d %d %lld %d\n", j, k, cnt, n);
if (cnt < n) n -= cnt;
else {
if (k < 3) k = j==6? k+1: 0;
printf("%d",j);
break;
}
}
} puts("");
} return 0;
}