【SDOI2014】数数(补)
见
[SDOI2014] 数数
简要题意:
我们称一个正整数N是幸运数,当且仅当它的十进制表示中不包含数字串集合S中任意一个元素作为子串。例如当S={22,333,0233}时,233是幸运数,2333、20233、3223都不是幸运数。给定N和S,计算不大于N的幸运数个数。
分析:
这道题是 数位dp + AC自动机,之前kzsn并没有学数位dp,所以学AC自动机的时候并不怎么会。
但如今,kzsn转型换代,终于懂了一点点数位dp的皮毛。
首先我们定个dp的状态 dp[pos][x],表示数位dp到第pos位,当前数字的状态位于AC自动机的x处。
这个状态怎么转移呢?
我们枚举下一位取的数字 $i$,如果$go[x][i]$处没有标记,即表示当前没有子串在当前数中,可以转移过去。
dp[pos][x] += dp[pos-1][go[x][i]];
那么这道题就做完了!!!
好耶!记忆化的数位dp是真的好写。
#includeusing namespace std; #define re register int #define int long long const int mo = 1e9+7; int go[2000][20], mark[2000], fail[2000], total; void insert(string s) { int p=0; for(re i=0, sz=s.length();i i) { int x=s[i]-'0'; if(!go[p][x])go[p][x]=++total; p=go[p][x]; } mark[p]=1; } void build() { queue<int>Q; for(re i=0;i<=9;++i)if(go[0][i])Q.push(go[0][i]); while(!Q.empty()) { int x=Q.front(); Q.pop(); mark[x]|=mark[fail[x]]; for(re i=0;i<=9;++i) { int t=go[x][i]; if(!t) go[x][i] = go[fail[x]][i]; else { Q.push(t); fail[t] = go[fail[x]][i]; } } } } int dp[2000][2000], num[2000]; int DFS(int pos, int x, int limit, int lead) { if(mark[x])return 0; if(!pos)return 1; int &ans = dp[pos][x]; if(!limit && ~ans) return ans; int ret = 0, up = limit ? num[pos] : 9; for(re i=0;i<=up;++i) { if(i == 0 && lead) ret += DFS(pos-1, x, limit && i == up, 1); else { int v = go[x][i]; if(!mark[v]) ret += DFS(pos-1, v, limit && i == up, 0); while(ret>=mo)ret-=mo; } } return limit ? ret : ans = ret; } int solve(string a) { memset(dp, -1, sizeof dp); int len = a.length(); for(re i=0;i 48; return DFS(len, 0, 1, 1); } signed main() { string n;int m; cin>>n>>m; while(m--) { string s; cin>>s; insert(s); } build(); printf("%lld", ((solve(n)-1)%mo+mo)%mo); return 0; }