【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是真的好写。

#include
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();ii)
    {
        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;i48;
    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;
}