ZOJ 3494 AC自动机+数位DP

题意

有一种BCD编码方案,求A到B范围内的数字的BCD编码有多少个不包含不能包含的字符串。

题解

数字范围这么大,很明显就能看出来是数位DP。基于AC自动机的数位DP使得数位DP容易了不少,因为AC自动机自带状态转移。在数位DP选取每一位的时候,基于AC自动机状态转移一下,如果转移到不能转移的状态,就直接返回-1。如果转移到合法状态,就继续DFS。唯一需要注意的就是需要大整数的减法,不过随便写写就行了,有前导0也无所谓,反正不影响数位DP。

代码

#include <iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<cmath>
#include<queue>
#include<string>
#include<set>
#include<map>
#include<bitset>
#include<stack>
#include<string>
#define UP(i,l,h) for(int i=l;i<h;i++)
#define DOWN(i,h,l) for(int i=h-1;i>=l;i--)
#define W(a) while(a)
#define MEM(a,b) memset(a,b,sizeof(a))
#define INF 0x3f3f3f3f
#define LL long long
#define MAXN 100010
#define MOD 1000000009
#define EPS 1e-10
#define int LL

using namespace std;
int ch[2010][10];
int sz;
int val[2010],f[2010];
char st[30];
int dig[220];
int dp[210][2010];
char a[210],b[210];
int code[10][4]= {{0,0,0,0},{0,0,0,1},{0,0,1,0},{0,0,1,1},{0,1,0,0},{0,1,0,1},{0,1,1,0},{0,1,1,1},{1,0,0,0},{1,0,0,1}};

int run(int u,int x) {
    if(val[u]) {
        return -1;
    }
    UP(i,0,4) {
        u=ch[u][code[x][i]];
        if(val[u]) {
            return -1;
        }
    }
    return u;
}

void insert() {
    int u=0;
    int len=strlen(st);
    UP(i,0,len) {
        int x=st[i]-'0';
        if(!ch[u][x]) {
            ch[u][x]=sz++;
        }
        u=ch[u][x];
    }
    val[u]=1;
//    cout<<"u"<<u<<endl;
}

void getFail() {
    MEM(f,0);
    queue<int> q;
    UP(i,0,10) {
        if(ch[0][i]) {
            q.push(ch[0][i]);
        }
    }
    W(!q.empty()) {
        int r=q.front();
        q.pop();
        UP(i,0,10) {
            int u=ch[r][i];
            if(!u) {
                ch[r][i]=ch[f[r]][i];
                continue;
            }
            q.push(u);
            int v=f[r];
            f[u]=ch[v][i];
            val[u]|=val[f[u]];
//            cout<<"val"<<u<<" "<<val[u]<<endl;
        }
    }
}

int dfs(int len,int u,bool first,bool up) {
    if(len==0) {
        if(!first)
            return 1;
        else
            return 0;
    }
    if(!up&&!first&&(dp[len][u]!=-1)) {
        return dp[len][u];
    }
    int n=up?dig[len]:9;
    int ans=0;
    UP(i,0,n+1) {
        if(i==0&&first) {
            ans=(ans+dfs(len-1,u,true,up&&i==n))%MOD;
        } else {
            int temp=run(u,i);
            if(temp==-1) {
                continue;
            }
            ans=(ans+dfs(len-1,temp,false,up&&i==n))%MOD;
        }
    }
    if(!up&&!first) {
        dp[len][u]=ans%MOD;
    }
    return ans%MOD;
}

int solve(char *x) {
    MEM(dp,-1);
    MEM(dig,0);
    int len=strlen(x);
    UP(i,0,len){
        dig[len-i]=x[i]-'0';
    }
    return dfs(len+1,0,true,true);
}

main() {
    int t;
    scanf("%lld",&t);
    W(t--) {
        MEM(ch,0);
        MEM(val,0);
        sz=1;
        int n;
        scanf("%lld",&n);
        W(n--) {
            scanf("%s",st);
            insert();
        }
        getFail();
        scanf("%s%s",&a,&b);
        int len=strlen(a);
        DOWN(i,len,0){
            if(a[i]=='0'){
                a[i]='9';
            }else{
                a[i]--;
                break;
            }
        }
        printf("%lld\n",(solve(b)-solve(a)+MOD)%MOD);
    }
}

/*
55
1
1001
1 10
*/
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值