[bzoj3530][SDOI2014]数数

题目描述

求不大于N的所有正整数中有多少个满足以下条件的数:给定字符串集合S,把该数当作字符串(没有前导0),集合S中没有任意一个字符串是该字符串的子集。
N的长度不超过1200,集合中所有字符串长度和不超过1500。

AC自动机上的DP

将集合内所有字符串建出一颗AC自动机。
那么,我们需要预处理一个这样的next[i,j]表示在结点i上接下要走j的话会调整到的结点是什么。(即预处理所有可能出现的调整结果)。
接下来就是数位DP了,设f[i,j,0]表示在第i位在AC自动机的结点j,当前比前i位比N小,那么f[i,j,1]就是与N相同,转移显然。
跳过所有状态满足当前结点可以匹配到一个单词(即S内的一个字符串),这样的结点j满足bz[j]=1(它是单词结点)或last[j]>0(它可以通过fail指针调整而匹配成功)。

参考程序

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<deque>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef long long ll;
const ll mo=1000000007;
struct dong{
    ll x,y;
};
deque<dong> dl;
ll next[1500+10][15],g[1500+10][15],fail[1500+10],last[1500+10];
bool bz[1500+10];
ll f[1200+10][1500+10][2];
ll i,j,k,l,t,n,m,top,tot,ans;
char s[1500+10],h[1500+10],ch;
dong zlt;
void insert(ll x,ll y){
    if (y>top){
        bz[x]=1;
        return;
    }
    if (g[x][h[y]-'0']==-1){
        g[x][h[y]-'0']=++tot;
        ll i;
        fo(i,0,9) g[tot][i]=-1;
    }
    insert(g[x][h[y]-'0'],y+1);
}
void bfs(){
    fo(i,0,9)
        if (g[0][i]!=-1){
            zlt.x=g[0][i];zlt.y=0;
            dl.push_back(zlt);
        }
    while (!dl.empty()){
        zlt=dl.front();
        dl.pop_front();
        k=zlt.x;l=zlt.y;
        fail[k]=l;
        if (bz[l]) last[k]=l;else last[k]=last[l];
        fo(i,0,9){
            if (g[k][i]==-1) continue;
            j=l;
            while (j&&g[j][i]==-1) j=fail[j];
            if (g[j][i]!=-1) j=g[j][i];
            zlt.x=g[k][i];zlt.y=j;
            dl.push_back(zlt);
        }
    }
}
void getnext(){
    fo(i,0,tot){
        fo(k,0,9){
            j=i;
            while (j&&g[j][k]==-1) j=fail[j];
            if (g[j][k]!=-1) j=g[j][k];
            next[i][k]=j;
        }
    }
}
int main(){
    //freopen("count.in","r",stdin);freopen("count.out","w",stdout);
    do{
        ch=getchar();
        if (ch=='\n') break;
        s[++n]=ch;
    }while (1);
    scanf("%lld",&m);
    ch=getchar();
    fo(i,0,9) g[0][i]=-1;
    fo(i,1,m){
        top=0;
        do{
            ch=getchar();
            if (ch=='\n') break;
            h[++top]=ch;
        }while (1);
        insert(0,1);
    }
    bfs();
    getnext();
    fo(i,1,s[1]-'0'-1) f[1][next[0][i]][0]=(f[1][next[0][i]][0]+1)%mo;
    f[1][next[0][s[1]-'0']][1]=(f[1][next[0][s[1]-'0']][1]+1)%mo;
    fo(i,1,n-1){
        fo(j,1,9) f[i+1][next[0][j]][0]=(f[i+1][next[0][j]][0]+1)%mo;
        fo(j,0,tot){
            if (bz[j]||last[j]) continue;
            if (f[i][j][0])
                fo(k,0,9) f[i+1][next[j][k]][0]=(f[i+1][next[j][k]][0]+f[i][j][0])%mo;
            if (f[i][j][1]){
                fo(k,0,s[i+1]-'0'-1) f[i+1][next[j][k]][0]=(f[i+1][next[j][k]][0]+f[i][j][1])%mo;
                f[i+1][next[j][s[i+1]-'0']][1]=(f[i+1][next[j][s[i+1]-'0']][1]+f[i][j][1])%mo;
            }
        }
    }
    ans=0;
    fo(i,0,tot)
        if (!bz[i]&&!last[i]) ans=(ans+f[n][i][0]+f[n][i][1])%mo;
    printf("%lld\n",ans);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值