Luogu P3311 [SDOI2014] 数数 题解
Description
给定一个数字串集合 \(S\),求不大于 \(n\) 的幸运数的个数。其中幸运数为不包含 \(S\) 中任意一个串的数。
Solution
数位dp+AC自动机
对于数字串集合,容易想到用AC自动机维护一下,建出trie图,这样就很方便了。
然后进行数位dp,记录当前在AC自动机上走到哪个节点了,如果到了某个串的末尾,就直接返回 \(0\),其他就是普通的数位dp了,只需要记一下有没有到上界,最后记得把 \(0\) 减去。
Code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#define ll long long
using namespace std;
const int N = 1510;
const int p = 1e9 + 7;
char s[N];
int n, a[N], m;
int trie[N][10], tot;
bool val[N];
int fail[N];
ll f[N][N];
void ins(char s[])
{
int len = strlen(s + 1);
int x = 0;
for(int i = 1; i <= len; i++)
{
int c = s[i] - '0';
if(!trie[x][c]) trie[x][c] = ++tot;
x = trie[x][c];
}
val[x] = 1;
return;
}
void build()
{
queue <int> que;
for(int i = 0; i < 10; i++)
if(trie[0][i]) que.push(trie[0][i]);
while(!que.empty())
{
int x = que.front();
que.pop();
for(int i = 0; i < 10; i++)
if(trie[x][i]) fail[trie[x][i]] = trie[fail[x]][i], que.push(trie[x][i]);
else trie[x][i] = trie[fail[x]][i];
}
for(int i = 1; i <= tot; i++)
for(int j = fail[i]; j; j = fail[j])
val[i] |= val[j];
trie[0][0] = 0;
return;
}
void init()
{
scanf("%s", s + 1);
n = strlen(s + 1);
for(int i = 1; i <= n; i++)
a[i] = s[n - i + 1] - '0';
scanf("%d", &m);
for(int i = 1; i <= m; i++)
scanf("%s", s + 1), ins(s);
build();
return;
}
ll dfs(int cur, int pos, bool lim)
{
if(val[pos]) return f[cur][pos] = 0;
if(!lim && ~f[cur][pos]) return f[cur][pos];
if(!cur) return 1;
int up = lim ? a[cur] : 9;
ll res = 0;
for(int i = 0; i <= up; i++)
res = (res + dfs(cur - 1, trie[pos][i], lim & (i == up))) % p;
if(!lim) f[cur][pos] = res;
return res;
}
int main()
{
init();
memset(f, -1, sizeof(f));
printf("%lld\n", dfs(n, 0, 1) - 1);
return 0;
}
$$A\ drop\ of\ tear\ blurs\ memories\ of\ the\ past.$$