洛谷 P3311 [SDOI2014]数数 AC自动机+dp

题目描述
我们称一个正整数N是幸运数,当且仅当它的十进制表示中不包含数字串集合S中任意一个元素作为其子串。例如当S=(22,333,0233)时,233是幸运数,2333、20233、3223不是幸运数。 给定N和S,计算不大于N的幸运数个数。

输入输出格式
输入格式:
输入的第一行包含整数N。 接下来一行一个整数M,表示S中元素的数量。 接下来M行,每行一个数字串,表示S中的一个元素。

输出格式:
输出一行一个整数,表示答案模 1 0 9 + 7 10^9+7 109+7的值。

输入输出样例
输入样例#1:
20
3
2
3
14
输出样例#1:
14
说明
下表中l表示N的长度,L表示S中所有串长度之和。
1 < =l < =1200 , 1 < =M < =100 ,1 < =L < =1500

分析:
对于S中的元素,可以把AC自动机建出来。并且每个节点维护一个 t a g tag tag表示fail链上是否存在一个节点是某个字符串的末尾。
然后就可以在AC自动机上面dp了。
f [ i ] [ j ] [ 0 / 1 ] f[i][j][0/1] f[i][j][0/1]表示前 i i i位,现在在AC自动机的第 j j j个节点(也许这个节点不代表 [ 1 , i ] [1,i] [1,i]的字符串,但一定是最长后缀),是否达到上限的答案。
转移枚举最后一位,并判断新加入字符后的串的 t a g tag tag是否为0。
最后原串不含前导0,我们直接在建好自动机后把 c h [ 0 ] . s o n [ 0 ] = 0 ch[0].son[0]=0 ch[0].son[0]=0即可,这样不会影响通过条 f a i l fail fail到达 c h [ 0 ] . s o n [ 0 ] ch[0].son[0] ch[0].son[0],只会影响直接到达。

代码:

// luogu-judger-enable-o2
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <queue>

const int maxn=1507;
const int mod=1e9+7;

using namespace std;

int n,m,cnt,ans;
int f[maxn][maxn][2],a[maxn];
char s[maxn];

struct node{
    int fail;
    int son[10];
    bool flag;
}t[maxn];

void build()
{
    int len=strlen(s+1),now=0;
    for (int i=1;i<=len;i++)
    {
        int c=s[i]-'0';
        if (!t[now].son[c]) t[now].son[c]=++cnt;
        now=t[now].son[c];
    }
    t[now].flag=1;
}

void getfail()
{
    queue <int> q;
    for (int i=0;i<10;i++)
    {
        if (t[0].son[i]) q.push(t[0].son[i]);
    }
    while (!q.empty())
    {
        int now=q.front();
        q.pop();
        t[now].flag|=t[t[now].fail].flag;
        for (int i=0;i<10;i++)
        {
            if (t[now].son[i])
            {
                t[t[now].son[i]].fail=t[t[now].fail].son[i];
                q.push(t[now].son[i]);
            }
            else t[now].son[i]=t[t[now].fail].son[i];
        }
    }
    t[0].son[0]=0;
}

void add(int &x,int y)
{
    x+=y;
    if (x>=mod) x-=mod;
}

int main()
{
    scanf("%s",s+1);
    n=strlen(s+1);
    for (int i=1;i<=n;i++) a[i]=s[i]-'0';
    scanf("%d",&m);
    for (int i=1;i<=m;i++)
    {
        scanf("%s",s+1);
        build();
    }
    getfail();
    f[0][0][1]=1;
    for (int i=1;i<=n;i++)
    {
        for (int j=0;j<=cnt;j++)
        {
            for (int k=0;k<=9;k++)
            {
                int next=t[j].son[k];
                if (t[next].flag) continue;
                add(f[i][next][0],f[i-1][j][0]);
                if (k<a[i]) add(f[i][next][0],f[i-1][j][1]);
                if (k==a[i]) add(f[i][next][1],f[i-1][j][1]);
            }
        }
    }
    ans=mod-1;
    for (int i=0;i<=cnt;i++) add(ans,f[n][i][0]),add(ans,f[n][i][1]);
    printf("%d\n",ans);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值