Description

​洛谷传送门​

Solution

看起来像是一道数位 \(dp\)。

但是怎么做呢?我们可以联想到​​文本生成器​​那道题。

这道题的思路也差不太多,就是在 \(AC\) 自动机上跑 \(dp\)。

那么如何判断一个数是否出现过集合 \(s\) 中的字符串呢?

这个也很简单,我们建 \(trie\) 时先标记一下每个字符串最后结尾的位置,建 \(fail\) 指针时,考虑到 \(fail\) 指向的位置是最长前缀,所以

End[x] |= End[fail[x]]


记忆化搜索时,在 \(trie\) 上跑,向下搜。

具体看代码吧。

Code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#define ll long long

using namespace std;

const ll mod = 1e9 + 7;
const ll N = 1600;
char n[N], s[N];
ll m;
ll trie[N][27], tot;
ll End[N], fail[N];
ll dp[N][N][2][2];

inline void insert(char s[]){
ll now = 0, len = strlen(s);
for(ll i = 0; i < len; i++){
ll c = s[i] - '0';
if(!trie[now][c]) trie[now][c] = ++tot;
now = trie[now][c];
}
End[now] = 1;
}

inline void build(){
queue <ll> q;
for(ll i = 0; i < 10; i++)
if(trie[0][i]) q.push(trie[0][i]);
while(!q.empty()){
ll now = q.front();
q.pop();
for(ll i = 0; i < 10; i++){
ll nxt = trie[now][i];
if(nxt){
fail[nxt] = trie[fail[now]][i];
End[nxt] |= End[fail[nxt]];
q.push(nxt);
}else trie[now][i] = trie[fail[now]][i];
}
}
}

inline ll dfs(ll len, ll now, ll flag, ll lim){
if(len <= 0) return !End[now];
if(End[now]) return 0;
if(dp[len][now][flag][lim] != -1) return dp[len][now][flag][lim];
ll x = lim ? n[len] - '0' : 9;
ll res = 0;
for(ll i = 0; i <= x; i++)
res = (res + dfs(len - 1, (flag && (!i)) ? 0 : trie[now][i], flag && (!i), lim && (n[len] - '0' == i)) + mod) % mod;
return dp[len][now][flag][lim] = res;
}

signed main(){
scanf("%s%lld", n + 1, &m);
for(ll i = 1; i <= m; i++){
scanf("%s", s);
insert(s);
}
build();
ll len = strlen(n + 1);
for(ll i = 1; i <= len / 2; i++)
swap(n[i], n[len - i + 1]);
memset(dp, -1, sizeof(dp));
printf("%lld\n", (dfs(len, 0, 1, 1) + mod - 1) % mod);
return 0;
}


End