P3311 数数
P3311 数数
题意:
我们称一个正整数 \(x\) 是幸运数,当且仅当它的十进制表示中不包含数字串集合 \(s\) 中任意一个元素作为其子串。例如当 \(s = \{22, 333, 0233\}\)时,\(233\)是幸运数,\(2333、20233、3223\) 不是幸运数。给定 \(n\)和 \(s\),计算不大于 \(n\)的幸运数个数。
答案对 \(10^9 + 7\) 取模。
思路:
数位dp + AC自动机
实现:
#include <stdio.h>
#include <algorithm>
#include <vector>
#include <cstring>
using namespace std;
const int N = 1505, mod = 1e9 + 7;
int idx = 1, tr[N][12], cnt[N], q[N], nex[N];
//首位是k,到第i位,匹配到j
long long f[N][N][2][2];
char s[N];
char num[N];
int m;
void insert()
{
int p = 0;
for (int i = 1; s[i]; i++)
{
int t = s[i] - '0';
if (!tr[p][t])
tr[p][t] = idx++;
p = tr[p][t];
}
cnt[p] = 1;
}
void build()
{
int hh = 1, tt = 0;
for (int i = 0; i <= 9; i++)
if (tr[0][i])
q[++tt] = tr[0][i];
while (hh <= tt)
{
int t = q[hh++];
for (int i = 0; i <= 9; i++)
{
int &p = tr[t][i];
if (!p)
p = tr[nex[t]][i];
else
{
nex[p] = tr[nex[t]][i];
q[++tt] = p;
cnt[p] |= cnt[nex[p]];
}
}
}
}
// pos:当前处理第几位,trie_pos当前的状态,limit当前是否被限制,lead是否有前导0
int dfs(int pos, int trie_pos, bool limit, bool lead)
{
if (!pos)
return !cnt[trie_pos];
if (cnt[trie_pos])
return 0;
if (!limit && !lead && f[pos][trie_pos][limit][lead] != -1)
return f[pos][trie_pos][limit][lead];
int up = limit ? (num[pos] - '0') : 9, res = 0;
for (int i = 0; i <= up; i++)
// 处理下一位 如果填0并且有前导0 前面顶了上限并且当前也顶上限下一位就会被限制 前面有前导0,或当前位0
res = ((int)res + dfs(pos - 1, (lead & !i) ? 0 : tr[trie_pos][i], limit & (i == up), lead & !i)) % mod;
//记忆化
f[pos][trie_pos][limit][lead] = res;
return res;
}
int main()
{
scanf("%s%d", num + 1, &m);
for (int i = 1; i <= m; i++)
{
scanf("%s", s + 1);
insert();
}
build();
int len = strlen(num + 1);
reverse(num + 1, num + len + 1);
memset(f, -1, sizeof f);
printf("%d\n", dfs(len, 0, 1, 1) - 1);
return 0;
}