P3311 [SDOI2014]数数
P3311 [SDOI2014]数数
思路:
因为这个题有多个串,所以我们可以考虑构建一个AC自动机,然后在AC自动机上面跑数位dp。
设\(dp(i,j)\)表示当前是第\(i\)位(从高到低),在AC自动机上面的第\(j\)个结点的合法情况总数。
同数位dp一样,看看当前这一位目前是否有限制,如果没限制,就是\(dp(i+1,fail[ch[j][k]+=dp(i,j)),0\leq k\leq9\);有限制的话给\(k\)加一个限制就好了。
注意一下因为不能出现,所以我们在AC自动机的结点不能到单词末尾结点,这个判断一下就行了。
对了,还要注意一个点就是要有后缀连接,比如对于串\(abcd,bc\),我们从\(a\)开始跑到\(c\)时,这时并不是单词某位结点,但\(bc\)是,这样的话其实我们也不能跑\(c\)这个结点的。当初这个点坑了我好几个小时。
代码如下
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1505, MAX = 10, MOD = 1e9 + 7;
char s[N], t[N];
int n;
int tot;
int ch[N][MAX], fail[N * MAX];
int lim[N] ;
ll dp[N][N][2] ;
bool f[N] ;
void insert(char *t) {
int L = strlen(t + 1) ;
int u = 0;
for(int i = 1 ;i <= L; i++) {
int v = t[i] - '0' ;
if(!ch[u][v]) ch[u][v] = ++tot;
u = ch[u][v] ;
}
f[u] = 1;
}
void build_ac() {
queue <int> q;
for(int i = 0;i < MAX; i++) if(ch[0][i]) q.push(ch[0][i]), fail[ch[0][i]] = 0;
while(!q.empty()) {
int u = q.front(); q.pop() ;
if(f[fail[u]]) f[u] = f[fail[u]] ;//后缀连接
for(int i = 0; i < MAX; i++) {
if(ch[u][i]) {
fail[ch[u][i]] = ch[fail[u]][i] ;
q.push(ch[u][i]) ;
} else ch[u][i] = ch[fail[u]][i] ;
}
}
ch[0][0] = 0;
}
void add(ll &x, ll y) {
x += y;
if(x >= MOD) x -= MOD ;
}
int main() {
scanf("%s", s + 1);
cin >> n;
for(int i = 1; i <= n; i++) scanf("%s", t + 1), insert(t);
build_ac();
int L = strlen(s + 1) ;
for(int i = 1; i <= L; i++) lim[L - i + 1] = s[i] - '0' ;
dp[L + 1][0][1] = 1;
for(int i = L + 1; i > 1; i--) {
for(int j = 0; j <= tot; j++) {
for(int l = 0; l < 2; l++) {
int up;
if(l == 0) up = 9;
else up = lim[i - 1] ;
for(int k = 0; k <= up; k++) {
int to = ch[j][k] ;
if(!f[to]) add(dp[i - 1][to][l & (k == up)], dp[i][j][l]) ;
}
}
}
}
ll ans = 0;
for(int i = 0; i <= tot; i++) add(ans, dp[1][i][0]), add(ans, dp[1][i][1]);
cout << ans - 1;
return 0;
}
重要的是自信,一旦有了自信,人就会赢得一切。