1. 程式人生 > 其它 >【SDOI2014】數數(補)

【SDOI2014】數數(補)

AC自動機(補坑了)

[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; }