Luogu P3311 [SDOI2014] 数数 题解

Link

Description

给定一个数字串集合 \(S\),求不大于 \(n\) 的幸运数的个数。其中幸运数为不包含 \(S\) 中任意一个串的数。

Solution

数位dp+AC自动机

对于数字串集合,容易想到用AC自动机维护一下,建出trie图,这样就很方便了。

然后进行数位dp,记录当前在AC自动机上走到哪个节点了,如果到了某个串的末尾,就直接返回 \(0\),其他就是普通的数位dp了,只需要记一下有没有到上界,最后记得把 \(0\) 减去。

Code

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#define ll long long

using namespace std;

const int N = 1510;
const int p = 1e9 + 7;
char s[N];
int n, a[N], m;
int trie[N][10], tot;
bool val[N];
int fail[N];
ll f[N][N];

void ins(char s[])
{
    int len = strlen(s + 1);
    int x = 0;
    for(int i = 1; i <= len; i++)
    {
        int c = s[i] - '0';
        if(!trie[x][c]) trie[x][c] = ++tot;
        x = trie[x][c];
    }
    val[x] = 1;
    return;
}

void build()
{
    queue <int> que;
    for(int i = 0; i < 10; i++)
        if(trie[0][i]) que.push(trie[0][i]);
    while(!que.empty())
    {
        int x = que.front();
        que.pop();
        for(int i = 0; i < 10; i++)
            if(trie[x][i]) fail[trie[x][i]] = trie[fail[x]][i], que.push(trie[x][i]);
            else trie[x][i] = trie[fail[x]][i];
    }
    for(int i = 1; i <= tot; i++)
        for(int j = fail[i]; j; j = fail[j])
            val[i] |= val[j];
    trie[0][0] = 0;
    return;
}

void init()
{
    scanf("%s", s + 1);
    n = strlen(s + 1);
    for(int i = 1; i <= n; i++)
        a[i] = s[n - i + 1] - '0';
    scanf("%d", &m);
    for(int i = 1; i <= m; i++)
        scanf("%s", s + 1), ins(s);
    build();
    return;
}

ll dfs(int cur, int pos, bool lim)
{
    if(val[pos]) return f[cur][pos] = 0;
    if(!lim && ~f[cur][pos]) return f[cur][pos];
    if(!cur) return 1;
    int up = lim ? a[cur] : 9;
    ll res = 0;
    for(int i = 0; i <= up; i++)
        res = (res + dfs(cur - 1, trie[pos][i], lim & (i == up))) % p;
    if(!lim) f[cur][pos] = res;
    return res;
}

int main()
{
    init();
    memset(f, -1, sizeof(f));
    printf("%lld\n", dfs(n, 0, 1) - 1);
    return 0;
}
posted @ 2021-10-08 22:01  Acestar  阅读(42)  评论(0编辑  收藏  举报