题解 - 启示录

题目

古代人认为 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\) 位(最高位)会新加进来什么数字,容易得到状态转移方程:

\[f_{i, 0} = 9\times(f_{i-1, 0}+f_{i-1, 1}+f_{i-1, 2}) \]

\(9\) 代表 \(0, 1, 2, 3, 4, 5, 7, 8, 9\)(因为这里 \(j = 0\),所以不能有 \(6\)。)分别加在后面的每个数的最高位。

\[f_{i, 1} = f_{i-1, 0} \]

代表把 \(6\) 加到后面的数的最高位。

\[f_{i, 2} = f_{i-1, 1} \]

代表把 \(6\) 加到后面的数的最高位。

\[f_{i, 3} = f_{i-1, 2} + 10*f_{i-1, 3} \]

代表把 \(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;
}
posted @ 2024-06-30 16:05  STA_Morlin  阅读(20)  评论(0编辑  收藏  举报