【SDOI2014】數數(補)
阿新 • • 發佈:2021-08-21
[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是真的好寫。
#include<bits/stdc++.h> using 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<sz;++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<len;++i) num[len-i] = a[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; }