【bzoj3530】[Sdoi2014]数数 AC自动机+数位dp
题目描述
我们称一个正整数N是幸运数,当且仅当它的十进制表示中不包含数字串集合S中任意一个元素作为其子串。例如当S=(22,333,0233)时,233是幸运数,2333、20233、3223不是幸运数。
给定N和S,计算不大于N的幸运数个数。
输入
输入的第一行包含整数N。
接下来一行一个整数M,表示S中元素的数量。
接下来M行,每行一个数字串,表示S中的一个元素。
输出
输出一行一个整数,表示答案模109+7的值。
样例输入
20
3
2
3
14
样例输出
14
题解
AC自动机+数位dp
同学的某道考试题的加强版。。。
由于给定的串多且复杂,要求不能匹配到这些串,所以可以对这些串建立Trie图。
然后设$f[i][j]$表示从$j$开始走$i$个节点,不触碰到危险节点(即得到的是非幸运数)的方案数。
那么如果$j$是危险节点则方案数为0,否则枚举$j$走出去的第一步,用$f[i-1][next[j][k]]$更新$f[i][j]$。
考虑数位dp的过程:
首先处理出位数不满$n$位的数的个数:枚举位数和最高位(非0),然后方案数即为$f[i][next[1][j]]$。
然后考虑位数满$n$位的数的个数:
考虑从高到底的每一位:从0到当前位-1是满的(即后面的数恰好从0到10^{位数}),因此可以直接从$f$中取出。再考虑当前位不满的情况,此时转化为了子问题,按照同样的方法处理即可。
注意最高位是不能包含前导0的,而确定最高位以后其余的位是可以包含前导0的。
时间复杂度$O(10nL)$
细节贼多。。。代码凑合着看吧。。。
#include <queue> #include <cstdio> #include <cstring> #define N 1510 #define mod 1000000007 using namespace std; queue<int> q; int next[N][10] , tot = 1 , fail[N] , tag[N] , f[N][N]; char s[N] , w[N]; void build() { int x , i; for(i = 0 ; i < 10 ; i ++ ) next[0][i] = 1; q.push(1); while(!q.empty()) { x = q.front() , q.pop() , tag[x] |= tag[fail[x]]; for(i = 0 ; i < 10 ; i ++ ) { if(next[x][i]) fail[next[x][i]] = next[fail[x]][i] , q.push(next[x][i]); else next[x][i] = next[fail[x]][i]; } } } int main() { int n , m , i , j , k , t , flag , ans = 0; scanf("%s%d" , s , &m) , n = strlen(s); for(i = 1 ; i <= m ; i ++ ) { scanf("%s" , w); for(j = 0 , t = 1 ; w[j] ; j ++ ) { if(!next[t][w[j] ^ '0']) next[t][w[j] ^ '0'] = ++tot; t = next[t][w[j] ^ '0']; } tag[t] = 1; } build(); for(i = 1 ; i <= tot ; i ++ ) f[1][i] = !tag[i]; for(i = 2 ; i <= n ; i ++ ) for(j = 1 ; j <= tot ; j ++ ) if(!tag[j]) for(k = 0 ; k < 10 ; k ++ ) f[i][j] = (f[i][j] + f[i - 1][next[j][k]]) % mod; for(i = 1 ; i < n ; i ++ ) for(j = 1 ; j < 10 ; j ++ ) ans = (ans + f[i][next[1][j]]) % mod; for(i = 0 , t = flag = 1 ; i < n ; i ++ ) { for(j = flag ; j < (s[i] ^ '0') ; j ++ ) ans = (ans + f[n - i][next[t][j]]) % mod; t = next[t][s[i] ^ '0']; if(tag[t]) break; flag = 0; } if(!tag[t]) ans = (ans + 1) % mod; printf("%d\n" , ans); return 0; }