[BZOJ3530][SDOI2014]数数

BZOJ
Luogu

sol

AC自动机做数位\(DP\)。首先位数小于\(n\)的位数的数只要满足没有不合法串即可,记\(f_{i,j}\)表示填了\(i\)个数,当前在\(AC\)自动机上编号为\(j\)的节点上的方案数,取答案\(\sum_{i=1}^{n-1}\sum_{j=0}^{tot}f_{i,j}\)。注意转移的时候是只能转移到自己\(Trie\)图上的儿子而不是儿子通过\(fail\)指针串起来的所有点。
位数等于\(n\)的,在前面那个状态上多加一维,表示是否已经严格小于那个数,然后按照数位\(DP\)的一般思路卡一卡就好了。

code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int mod = 1e9+7;
const int N = 2005;
int gi()
{
    int x=0,w=1;char ch=getchar();
    while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    if (ch=='-') w=0,ch=getchar();
    while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return w?x:-x;
}

int n,m,l,tot,ch[10][N],fail[N],frb[N],dp[2][N][N],ans;
char s[N],c[N];
queue<int>Q;
void Insert()
{
    scanf("%s",c);l=strlen(c);
    int x=0;
    for (int i=0;i<l;i++)
    {
        if (!ch[c[i]-'0'][x]) ch[c[i]-'0'][x]=++tot;
        x=ch[c[i]-'0'][x];
    }
    frb[x]=1;
}
void Get_Fail()
{
    for (int i=0;i<10;i++) if (ch[i][0]) Q.push(ch[i][0]);
    while (!Q.empty())
    {
        int u=Q.front();Q.pop();
        for (int i=0;i<10;i++)
            if (ch[i][u]) fail[ch[i][u]]=ch[i][fail[u]],Q.push(ch[i][u]);
            else ch[i][u]=ch[i][fail[u]];
        frb[u]|=frb[fail[u]];
    }
}
void DP()
{
    dp[0][0][0]=1;
    for (int i=0;i<n;i++)
        for (int j=0;j<=tot;j++)
            if (!frb[j])
                for (int k=0;k<10;k++)
                    if (i+k&&!frb[ch[k][j]])
                        (dp[0][i+1][ch[k][j]]+=dp[0][i][j])%=mod;
    for (int i=1;i<n;i++)
        for (int j=0;j<=tot;j++)
            (ans+=dp[0][i][j])%=mod;
    memset(dp,0,sizeof(dp));
    dp[1][0][0]=1;
    for (int i=0;i<n;i++)
        for (int j=0;j<=tot;j++)
            if (!frb[j])
                for (int k=0;k<10;k++)
                    if (i+k&&!frb[ch[k][j]])
                    {
                        (dp[0][i+1][ch[k][j]]+=dp[0][i][j])%=mod;
                        if (k==s[i+1]-'0') (dp[1][i+1][ch[k][j]]+=dp[1][i][j])%=mod;
                        if (k<s[i+1]-'0') (dp[0][i+1][ch[k][j]]+=dp[1][i][j])%=mod;
                    }
    for (int j=0;j<=tot;j++) (ans+=(dp[0][n][j]+dp[1][n][j])%mod)%=mod;
}
int main()
{
    scanf("%s",s+1);n=strlen(s+1);
    scanf("%d",&m);
    for (int i=1;i<=m;i++) Insert();
    Get_Fail();
    DP();
    printf("%d\n",ans);
    return 0;
}

转载于:https://www.cnblogs.com/zhoushuyu/p/8350090.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值