BZOJ3530:[SDOI2014]数数——题解

https://www.lydsy.com/JudgeOnline/problem.php?id=3530

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

给定N和S,计算不大于N的幸运数个数。

参考:https://www.luogu.org/blog/user17952/solution-p3311

绝对(对我来说)是道难题,尤其是很久不写AC自动机与数位dp的我……

首先n特别大,必须得考虑数位dp。

其次给了一堆串,要求不能出现这些串,先建AC自动机再说。

于是得出了一个愉快的结论:在AC自动机上跑数位dp(写到这里我只想用CaO来表达我的心情)

设$f[i][j][0/1]$为填到$i$数位,当前在AC自动机的$j$节点处,已经小于等于/大于到$i$位的原数。

此时需要注意:为了方便(因为参考是这么写的233),我们正着扫n而非以前的套路倒着扫n,这样做会带来一些问题,于是我们将小于等于也拆开,分别用0和1表示。

(PS:那么反着扫不知道是否可行……以及如果要反着扫的话可能需要反着存串。)

dp式子就不细讲了,直接看代码理解吧。唯一要注意的是不能有前导0所以第一层我们要特殊处理。

#include<map>
#include<cmath>
#include<stack>
#include<queue>
#include<cstdio>
#include<cctype>
#include<vector>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int p=1e9+7;
const int L=1505;
struct AC{
    int fail,a[10];
    bool ed;
}tr[L];
char s[L],s0[L];
int tot,f[L][L][3];
void insert(){
    int len=strlen(s),now=0;
    for(int i=0;i<len;i++){
    int w=s[i]-'0';
    if(!tr[now].a[w])tr[now].a[w]=++tot;
    now=tr[now].a[w];
    }
    tr[now].ed=1;
}
void getfail(){
    queue<int>q;
    tr[0].fail=0;
    for(int i=0;i<10;i++){
    int u=tr[0].a[i];
    if(u){
        tr[u].fail=0;q.push(u);
    }
    }
    while(!q.empty()){
    int u=q.front();q.pop();
    for(int i=0;i<10;i++){
        if(tr[u].a[i]){
        int v=tr[u].a[i];
        tr[v].fail=tr[tr[u].fail].a[i];
        tr[v].ed|=tr[tr[tr[u].fail].a[i]].ed;
        q.push(v);
        }else tr[u].a[i]=tr[tr[u].fail].a[i];
    }
    }
}
inline int add(int x,int y){
    x+=y;if(x>=p)x-=p;return x;
}
int main(){
    int n,m;
    scanf("%s%d",s0,&m);
    while(m--){
    scanf("%s",s);insert();
    }
    getfail();
    n=strlen(s0);
    int ans=0;
    for(int i=1;i<10;i++){
    int v=tr[0].a[i],w=s0[0]-'0';
    if(!tr[v].ed){
        if(i<w)f[0][v][0]++;
        else if(i==w)f[0][v][1]++;
        else f[0][v][2]++;
    }
    }
    for(int i=1;i<n;i++){
    int w=s0[i]-'0';
    for(int j=0;j<=tot;j++){
        if(f[i-1][j][0]||f[i-1][j][1]||f[i-1][j][2])
        for(int k=0;k<10;k++){
            int v=tr[j].a[k];
            if(tr[v].ed)continue;
            if(k<w){
            f[i][v][0]=add(f[i][v][0],add(f[i-1][j][0],f[i-1][j][1]));
            f[i][v][2]=add(f[i][v][2],f[i-1][j][2]);
            }else if(k==w){
            f[i][v][0]=add(f[i][v][0],f[i-1][j][0]);
            f[i][v][1]=add(f[i][v][1],f[i-1][j][1]);
            f[i][v][2]=add(f[i][v][2],f[i-1][j][2]);
            }else{
            f[i][v][0]=add(f[i][v][0],f[i-1][j][0]);
            f[i][v][2]=add(f[i][v][2],add(f[i-1][j][2],f[i-1][j][1]));
            }
        }
    }
    }
    for(int i=0;i<n;i++){
    for(int j=0;j<=tot;j++){
        ans=add(ans,add(f[i][j][0],f[i][j][1]));
        if(i<n-1)ans=add(ans,f[i][j][2]);
    }
    }
    printf("%d\n",ans);
    return 0;
}

+++++++++++++++++++++++++++++++++++++++++++

+本文作者:luyouqi233。               +

+欢迎访问我的博客:http://www.cnblogs.com/luyouqi233/ +

+++++++++++++++++++++++++++++++++++++++++++

posted @ 2018-06-20 19:18  luyouqi233  阅读(194)  评论(0编辑  收藏  举报