3530: [Sdoi2014]数数
Time Limit: 10 Sec Memory Limit: 512 MBSubmit: 1226 Solved: 618
[ Submit][ Status][ Discuss]
Description
我们称一个正整数N是幸运数,当且仅当它的十进制表示中不包含数字串集合S中任意一个元素作为其子串。例如当S=(22,333,0233)时,233是幸运数,2333、20233、3223不是幸运数。
给定N和S,计算不大于N的幸运数个数。
Input
输入的第一行包含整数N。
接下来一行一个整数M,表示S中元素的数量。
接下来M行,每行一个数字串,表示S中的一个元素。
Output
输出一行一个整数,表示答案模109+7的值。
Sample Input
3
2
3
14
Sample Output
HINT
下表中l表示N的长度,L表示S中所有串长度之和。
1 < =l < =1200 , 1 < =M < =100 ,1 < =L < =1500
Source
又是这种不能包含xxx长度为yyy的字符串有多少个的题... 按照之前的做法想了个矩阵乘法的做法, 发现不仅时间要T(由于<=所以无法快速幂)而且无法处理<=n的正整数这个条件.
那么对于这种计数题显然只能AC自动机上DP辣... 考虑<=n, n很大显然不能作为dp数组中的某一维, 那么怎么记录<=n这个条件是否满足呢? 可以考虑像数位DP那样直接记录当前是否顶位来判断. 而且关于幸运数字中的0还得和前导零区分开, 于是又得拿一维来记录当前是否是前导零状态. 那么dp数组就呼之欲出了, dp[i][j][k][t]表示当前长度为i走到j号节点是否顶位是否是前导零的字符串数. 转移比较简单.
有几个需要注意的是刷表式过程中如果当前既是前导零状态下一个又是零的话那么只能转移到0号节点去, 而不是当前节点0号儿子去, 这点需要注意. 还有就是第一个数字是前导零状态但同时也有顶位限制(代码中!i的部分), 这个特判一下就行了.
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1505;
const int mod = 1e9 + 7;
bool vis[maxn];
char ss[maxn], str[maxn];
int n, m, tot, ans, cnt, f[maxn][maxn][2][2];
int c[maxn][11], fail[maxn];
inline void insert() {
int p = 0;
for (int i = 0; ss[i]; ++ i) {
int idx = ss[i] - '0';
if (!c[p][idx]) c[p][idx] = ++ tot;
p = c[p][idx];
}
vis[p] = true;
}
queue<int> q;
inline void bfs() {
for (int i = 0; i < 10; ++ i)
if (c[0][i]) q.push(c[0][i]);
while (!q.empty()) {
int u = q.front(); q.pop();
for (int i = 0; i < 10; ++ i) {
int &v = c[u][i];
if (!v) {v = c[fail[u]][i]; continue;}
fail[v] = c[fail[u]][i], vis[v] |= vis[fail[v]];
q.push(v);
}
}
}
inline void add(int &a, int b) {
a += b;
if (a >= mod) a -= mod;
}
inline void dp() {
f[0][0][0][0] = 1;
for (int i = 0; i < n; ++ i)
for (int j = 0; j <= tot; ++ j)
for (int k = 0; k < 2; ++ k)
for (int t = 0; t < 2; ++ t)
if (f[i][j][k][t]) {
int lim = (k || !i) ? str[i] - '0' : 9;
for (int p = 0; p <= lim; ++ p) {
int son = c[j][p];
if (!p && !t) add(f[i + 1][0][0][0], f[i][j][k][t]);
if (vis[son] || (!p && !t)) continue;
add(f[i + 1][son][(k || !i) && (p == lim)][p || t], f[i][j][k][t]);
}
}
for (int i = 0; i <= tot; ++ i)
if (!vis[i])
for (int j = 0; j < 2; ++ j)
add(ans, f[n][i][j][1]);
}
int main() {
scanf("%s", str);
n = strlen(str);
scanf("%d", &m);
for (int i = 0; i < m; ++ i) {
scanf("%s", ss);
insert();
}
bfs(), dp();
printf("%d\n", ans);
}