POJ 3208 Apocalypse Someday
题目大意:
输出第N大的包含666这个子串的正整数. N不大于50000000.
简要分析:
第一反应是某种数位DP, 第二反应是要匹配666, 建个自动机...只有4种状态的自动机. 0表示没有666且当前没有6, 1表示没有666且当前有一个6, 2表示没有666且当前有两个6, 3表示有666, 于是可以人肉出转移表trans. 定义f[i][j]为在j状态的字符串后输入i个字符能达到目标的方案数. 初始态是f[0][3] = 1, 转移方程f[i][j] = sigma(f[i-1][trans[j][k]]), k为10种转移. 构造答案时先算出位数dig, 再从dig高位往低位一位一位确定就好了.
代码实现:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <cstdio>
2 #include <cstdlib>
3 #include <cstring>
4 #include <algorithm>
5 using namespace std;
6
7 typedef long long val_t;
8 const int MAX_DIG = 10, MAX_NUM = 9, STATE = 4;
9 const int trans[4][10] = {
10 {0, 0, 0, 0, 0, 0, 1, 0, 0, 0},
11 {0, 0, 0, 0, 0, 0, 2, 0, 0, 0},
12 {0, 0, 0, 0, 0, 0, 3, 0, 0, 0},
13 {3, 3, 3, 3, 3, 3, 3, 3, 3, 3}
14 };
15 val_t f[MAX_DIG + 1][STATE];
16 int T, n;
17
18 int main() {
19 f[0][3] = 1;
20 for (int i = 1; i <= MAX_DIG; i ++)
21 for (int j = 0; j < STATE; j ++)
22 for (int k = 0; k <= MAX_NUM; k ++)
23 f[i][j] += f[i - 1][trans[j][k]];
24 for (scanf("%d", &T); T -- && scanf("%d", &n) != EOF; ) {
25 int dig = 0;
26 for (int i = 1; i <= MAX_DIG; i ++)
27 if (f[i][0] >= n) {
28 dig = i;
29 break;
30 }
31 int st = 0;
32 for (int i = dig; i >= 1; i --) {
33 int cnt = 0;
34 for (int j = 0; j <= MAX_NUM; j ++) {
35 cnt += f[i - 1][trans[st][j]];
36 if (cnt >= n) {
37 printf("%d", j);
38 cnt -= f[i - 1][trans[st][j]];
39 n -= cnt;
40 st = trans[st][j];
41 break;
42 }
43 }
44 }
45 printf("\n");
46 }
47 return 0;
48 }