ZOJ3494--BCD Code(AC自动机+数位DP)

题意:有T组测试数据每组一个整数n代表有n个不合格的二进制编码,将所有数字转化为BCD码 判断区间l,r之间有多少合格的数字 (一个数只要转化为BCD码后中间有不合格的BCD码该数据就不合格)
题解:先用AC自动机将不合格二进制串处理好然后定义一个bcd[i][j]二维数组 表示在数字中前一个数为AC自动机树上的节点i时下一个位数为j(0~9)时合不合法 为-1表示不合法;
dp[i][j] 表示数字的第几位 j表示前一个数为AC自动机树上的节点i时有多少合格的数
(借鉴于大佬)代码:
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <cctype>
#include <string>
#include <iostream>
#include <vector>
#include <map>
#include <set>
#include <stack>
#include <queue>
using namespace std;
#define clr(a,b) memset(a,b,sizeof(a))
#define reg      register
#define il       inline
typedef long long ll;
const int maxn = 2005;
const int mod= 1000000009;
char str[205];
class AC {
    public:
    int next[maxn][3], fail[maxn], tag[maxn];
    int dig[205],bcd[maxn][10];
    int root, tot;
    ll dp[205][maxn];
    void init() {
        clr(dp,-1);
        clr(next,-1);
        clr(tag,0);
        root =tot=0;
    }
    void insert(char *buf) {
        int len = strlen(buf);
        int now = root;
        for(reg int i = 0; i < len; ++i) {
            int id = buf[i] - '0';
            now=next[now][id]!=-1?next[now][id]:(next[now][id]=++tot);
        }
        tag[now] = 1;
    }
    void build() {
        queue<int> q;
        fail[root] = root;
        for(reg int i = 0; i < 3; ++i) {
            if(next[root][i] == -1) {
                next[root][i] = root;
            }
            else {
                fail[next[root][i]] = root;
                q.push(next[root][i]);
            }
        }
        while(!q.empty()) {
            int now = q.front();
            q.pop();
            tag[now] |= tag[fail[now]];
            for(reg int i = 0; i < 3; ++i) {
                if(next[now][i] == -1) {
                    next[now][i] = next[fail[now]][i];
                } else {
                    fail[next[now][i]] = next[fail[now]][i];
                    q.push(next[now][i]);
                }
            }
        }
    }
    void getbcd() {
        for(reg int i = 0; i <=tot; ++i) {
            for(reg int j = 0; j < 10; ++j) {
                bcd[i][j] = i;
                if(tag[i])
                    bcd[i][j] = -1;
                int now = i;
                for(reg int k = 3; k >= 0; --k) {//从做到右匹配
                    int x = (j >> k) & 1;
                    if(tag[next[now][x]]) {
                        bcd[i][j] = -1;
                        break;
                    }
                    else bcd[i][j] = next[now][x];
                    now = next[now][x];
                }
            }
        }
    }
    ll dfs(int pos, int cur, int limit, int nozero) {

        if(pos == 0) return 1;
        if(dp[pos][cur] != -1 && !limit && nozero) return dp[pos][cur];
        ll res = 0;
        for(reg int i = 0; i <= (limit ? dig[pos] : 9); ++i) {
            if(!nozero && i == 0)
                res += dfs(pos-1, cur, limit&&i==dig[pos], nozero);
            else if(bcd[cur][i] != -1)
                res += dfs(pos-1, bcd[cur][i],limit&&i==dig[pos],1);
            res %= mod;
        }
        if(!limit && nozero) dp[pos][cur] = res;
        return res;
    }
    ll solve(char *buf) {
        int len = strlen(buf);
        for(reg int i = 0; i < len; ++i) {
            dig[len - i] = buf[i] - '0';
        }
        return dfs(len, root, 1, 0);
    }
}ac;
il void dec(char *buf) {//左区间后移一位
    int len = strlen(buf);
    for(reg int i = len-1; i >= 0; --i) {
        if(buf[i] > '0') {
            buf[i] --;
            return ;
        }
        else buf[i] = '9';
    }
}
int main() {
    int t,n;
    scanf("%d",&t);
    while(t--) {
        ac.init();
        scanf("%d",&n);
       while(n--)
       {
            scanf("%s",str);
            ac.insert(str);
        }
        ac.build();
        ac.getbcd();
        scanf("%s",str);
        dec(str);
        ll ans = ac.solve(str);
        scanf("%s",str);
        printf("%lld\n",(ac.solve(str)-ans+mod)%mod);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值