这里的 k 就是当前枚举的下一位的值,而 maxx 就是当前能取到的最高位。因为如果 op=1,那么你在这一位上取的值一定不能大于求解的数字上该位的值,否则则没有限制。
我们发现,尽管前缀所选择的状态不同,而 f 的三个参数相同,答案就是一样的。为了防止这个答案被计算多次,可以使用 记忆化搜索 的方式实现。
实现
参考代码
int dfs(int x, int st, int op) // op=1 =; op=0 <{ if (!x) return 1; if (!op && ~f[x][st]) return f[x][st]; int maxx = op ? dim[x] : 9, ret = 0; for (int i = 0; i <= maxx; i++) { if (abs(st - i) < 2) continue; if (st == 11 && i == 0) ret += dfs(x - 1, 11, op & (i == maxx)); else ret += dfs(x - 1, i, op & (i == maxx)); } if (!op) f[x][st] = ret; return ret;}int solve(int x) { memset(f, -1, sizeof f); dim.clear(); dim.push_back(-1); int t = x; while (x) { dim.push_back(x % 10); x /= 10; } return dfs(dim.size() - 1, 11, 1);}
题面:我们称一个正整数 x 是幸运数,当且仅当它的十进制表示中不包含数字串集合 S 中任意一个元素作为其子串。例如当 S={22,333,0233} 时,233233 是幸运数,23332333、2023320233、32233223 不是幸运数。给定 n 和 S,计算不大于 n 的幸运数个数。答案对 109+7 取模。
阅读题面发现,如果将数字看成字符串,那么这就是需要完成一个多模匹配,自然而然就想到 AC 自动机。普通数位 DP 中,先从高到低枚举数位,再枚举每一位都填什么,在这道题中,我们也就自然地转化为枚举已经填好的位数,再枚举此时停在 AC 自动机上的哪个节点,然后从当前节点转移到它在 AC 自动机上的子节点。
设 f(i,j,0/1) 表示当前从高到低已经填了 i 位(即在 AC 自动机上走过了 i 条边),此时停在标号为 j 的节点上,当前是否正好贴着上界。
至于题目中的「不包含」条件,只需在 AC 自动机上给每个模式串的结尾节点都打上标记,DP 过程中一旦遇上这些结尾节点就跳过即可。
转移很好想,详见代码主函数部分。
实现
参考代码
#include <cstdio>#include <cstring>#include <queue>using namespace std;using ll = long long;constexpr int N = 1505;constexpr int mod = 1000000007;int n, m;char s[N], c[N];int ch[N][10], fail[N], ed[N], tot, len;void insert() { int now = 0; int L = strlen(s); for (int i = 0; i < L; ++i) { if (!ch[now][s[i] - '0']) ch[now][s[i] - '0'] = ++tot; now = ch[now][s[i] - '0']; } ed[now] = 1;}queue<int> q;void build() { for (int i = 0; i < 10; ++i) if (ch[0][i]) q.push(ch[0][i]); while (!q.empty()) { int u = q.front(); q.pop(); for (int i = 0; i < 10; ++i) { if (ch[u][i]) { fail[ch[u][i]] = ch[fail[u]][i], q.push(ch[u][i]), ed[ch[u][i]] |= ed[fail[ch[u][i]]]; } else ch[u][i] = ch[fail[u]][i]; } } ch[0][0] = 0;}ll f[N][N][2], ans;void add(ll &x, ll y) { x = (x + y) % mod; }int main() { scanf("%s", c); n = strlen(c); scanf("%d", &m); for (int i = 1; i <= m; ++i) scanf("%s", s), insert(); build(); f[0][0][1] = 1; for (int i = 0; i < n; ++i) { for (int j = 0; j <= tot; ++j) { if (ed[j]) continue; for (int k = 0; k < 10; ++k) { if (ed[ch[j][k]]) continue; add(f[i + 1][ch[j][k]][0], f[i][j][0]); if (k < c[i] - '0') add(f[i + 1][ch[j][k]][0], f[i][j][1]); if (k == c[i] - '0') add(f[i + 1][ch[j][k]][1], f[i][j][1]); } } } for (int j = 0; j <= tot; ++j) { if (ed[j]) continue; add(ans, f[n][j][0]); add(ans, f[n][j][1]); } printf("%lld\n", ans - 1); return 0;}