zoj 3494 BCD Code(AC自动机+数位dp)

题目链接:zoj 3494 BCD Code

题目大意:给定n个2进制串,然后有一个区间l,r,问说l,r之间有多少个数转换成BCD二进制后不包含上面的2进制串。

解题思路:AC自动机+数位dp。先对禁止串建立AC自动机,所有的单词节点即为禁止通行的节点。接着进行数位dp,

用solve(r) - solve(l-1), 这里的l需要用到大数减法。dp[i][j]表示第i为移动到节点j的可行方案数,每次枚举下一位数字,因

为是BCD二进制,所以每位数要一次性移动4个字符,中途有经过禁止点都是不行的。然后用一个eq,mv标记相等的情

况,相等的情况也有可能是不可以的,即eq = 0时。注意处理前导0的情况,因为有些禁止串可以由0组成,所以对于前

导0不能在AC自动机上移动,在每一位的时候单独拿出来考虑。

#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#include <iostream>
#include <algorithm>

using namespace std;

const int maxn = 2005;
const int mod = 1e9 + 9;
const int sigma_size = 2;
const char sign[12][5] = {"0000", "0001", "0010", "0011", "0100", "0101", "0110", "0111", "1000", "1001"};

struct Aho_Corasick {
    int sz, g[maxn][sigma_size];
    int tag[maxn], fail[maxn], last[maxn];

    int dp[205][maxn];

    void init();
    int idx(char ch);
    void insert(char* str, int k);
    void getFail();
    void match(char* str);
    void put(int x, int y);
    int solve(int* a, int n);
    int move(int u, int v);
}AC;

int N, num[205];

int getNum(int* a) {
    char s[205];
    scanf("%s", s);
    int n = strlen(s), mv = 0;
    while (mv < n && s[mv] == ‘0‘) mv++;

    n -= mv;
    if (n == 0)
        num[n++] = 0;
    else {
        for (int i = 0; i < n; i++)
            num[i] = s[i+mv] - ‘0‘;
    }
    return n;
}

void del(int* a, int& n) {
    if (n == 0)
        return;

    a[n-1]--;
    for (int i = n-1; i >= 0; i--) {
        if (a[i] < 0) {
            a[i] += 10;
            a[i-1]--;
        } else
            break;
    }

    if (a[0] == 0) {
        for (int i = 0; i < n; i++)
            a[i] = a[i+1];
        n--;
    }
}

int main () {
    int cas, n;
    char w[50];
    scanf("%d", &cas);

    while (cas--) {
        AC.init();
        scanf("%d", &n);
        for (int i = 1; i <= n; i++) {
            scanf("%s", w);
            AC.insert(w, i);
        }
        AC.getFail();

        N = getNum(num);
        del(num, N);
        int l = AC.solve(num, N);

        N = getNum(num);
        int r = AC.solve(num, N);

        printf("%d\n", (r - l + mod) % mod);
    }
    return 0;
}

int Aho_Corasick::move(int u, int id) {
    for (int i = 0; i < 4; i++) {
        int v = idx(sign[id][i]);
        while (u && g[u][v] == 0)
            u = fail[u];
        u = g[u][v];

        if (tag[u] || last[u])
            return -1;
    }
    return u;
}

int Aho_Corasick::solve(int* a, int n) {
    if (n == 0)
        return 0;
    memset(dp, 0, sizeof(dp));

    int eq = 1, mv = 0;
    for (int i = 0; i < n; i++) {
        for (int x = 0; x < sz; x++) {
            if (tag[x] || last[x])
                continue;

            for (int j = 0; j < 10; j++) {
                int u = move(x, j);
                if (u == -1)
                    continue;
                dp[i+1][u] = (dp[i+1][u] + dp[i][x]) % mod;
            }
        }

        if (i) {
            for (int j = 1; j < 10; j++) {
                int u = move(0, j);
                if (u == -1)
                    continue;
                dp[i+1][u] = (dp[i+1][u] + 1) % mod;
            }
        }

        if (eq) {
            for (int j = (i == 0 ? 1 : 0); j < a[i]; j++) {
                int u = move(mv, j);
                if (u == -1)
                    continue;
                dp[i+1][u] = (dp[i+1][u] + 1) % mod;
            }

            int u = move(mv, a[i]);
            if (u == -1) eq = 0;
            mv = u;
        }
    }

    int ans = eq;
    for (int i = 0; i < sz; i++) {
        if (tag[i] || last[i])
            continue;
        ans = (ans + dp[n][i]) % mod;
    }
    return ans;
}

void Aho_Corasick::init() {
    sz = 1;
    tag[0] = 0;
    memset(g[0], 0, sizeof(g[0]));
}

int Aho_Corasick::idx(char ch) {
    return ch - ‘0‘;
}

void Aho_Corasick::put(int x, int y) {
}

void Aho_Corasick::insert(char* str, int k) {
    int u = 0, n = strlen(str);

    for (int i = 0; i < n; i++) {
        int v = idx(str[i]);
        if (g[u][v] == 0) {
            tag[sz] = 0;
            memset(g[sz], 0, sizeof(g[sz]));
            g[u][v] = sz++;
        }
        u = g[u][v];
    }
    tag[u] = k;
}

void Aho_Corasick::match(char* str) {
    int n = strlen(str), u = 0;
    for (int i = 0; i < n; i++) {
        int v = idx(str[i]);
        while (u && g[u][v] == 0)
            u = fail[u];

        u = g[u][v];

        if (tag[u])
            put(i, u);
        else if (last[u])
            put(i, last[u]);
    }
}

void Aho_Corasick::getFail() {
    queue<int> que;

    for (int i  = 0; i < sigma_size; i++) {
        int u = g[0][i];
        if (u) {
            fail[u] = last[u] = 0;
            que.push(u);
        }
    }

    while (!que.empty()) {
        int r = que.front();
        que.pop();

        for (int i = 0; i < sigma_size; i++) {
            int u = g[r][i];

            if (u == 0) {
                g[r][i] = g[fail[r]][i];
                continue;
            }

            que.push(u);
            int v = fail[r];
            while (v && g[v][i] == 0)
                v = fail[v];

            fail[u] = g[v][i];
            last[u] = tag[fail[u]] ? fail[u] : last[fail[u]];
        }
    }
}
时间: 2024-10-16 18:10:47

zoj 3494 BCD Code(AC自动机+数位dp)的相关文章

zoj3494BCD Code(ac自动机+数位dp)

l链接 这题想了好一会呢..刚开始想错了,以为用自动机预处理出k长度可以包含的合法的数的个数,然后再数位dp一下就行了,写到一半发现不对,还要处理当前走的时候是不是为合法的,这一点无法移到trie树上去判断. 之后想到应该在trie树上进行数位dp,走到第i个节点且长度为j的状态是确定的,所以可以根据trie树上的节点来进行确定状态. dp[i][j]表示当前节点为i,数第j位时可以包含多少个合法的数. 1 #include <iostream> 2 #include<cstdio>

HDU 4518 ac自动机+数位dp

吉哥系列故事--最终数 Time Limit: 500/200 MS (Java/Others)    Memory Limit: 65535/32768 K (Java/Others) Total Submission(s): 304    Accepted Submission(s): 102 Problem Description 在2012年腾讯编程马拉松比赛中,吉哥解决了一道关于斐波那契的题目,这让他非常高兴,也更加燃起了它对数学特别是斐波那契数的热爱.现在,它又在思考一个关于斐波那契

【bzoj3530】[Sdoi2014]数数 AC自动机+数位dp

题目描述 我们称一个正整数N是幸运数,当且仅当它的十进制表示中不包含数字串集合S中任意一个元素作为其子串.例如当S=(22,333,0233)时,233是幸运数,2333.20233.3223不是幸运数.给定N和S,计算不大于N的幸运数个数. 输入 输入的第一行包含整数N.接下来一行一个整数M,表示S中元素的数量.接下来M行,每行一个数字串,表示S中的一个元素. 输出 输出一行一个整数,表示答案模109+7的值. 样例输入 20 3 2 3 14 样例输出 14 题解 AC自动机+数位dp 同学

ZOJ 3494 BCD Code (数位DP,AC自动机)

题意: 将一个整数表示成4个bit的bcd码就成了一个01串,如果该串中出现了部分病毒串,则是危险的.给出n个病毒串(n<=100,长度<21),问区间[L,R]中有几个数字是不含病毒串的(结果需要取模)?(0<L<=R<=10200) 思路: 区间非常大,怎样暴力统计都是不科学的.首先确定状态,按传统,一维必定是位数,二维就是压缩的状态了,如果长度为20个bit的话,200*104万的数组是不行的.类似多模式串匹配问题,病毒串可以构建成AC自动机,那么每个点可以代表一个独立

ZOJ 3494 BCD Code (AC自己主动机 + 数位DP)

题目链接:BCD Code 解析:n个病毒串.问给定区间上有多少个转换成BCD码后不包括病毒串的数. 很奇妙的题目. . 经典的 AC自己主动机 + 数位DP 的题目. 首先使用AC自己主动机,得到bcd[i][j]表示状态i,加了数字j以后到达的状态.为-1表示不能转移 然后就是数位DP了 注意记录为0的状态 AC代码: #include <cstdio> #include <iostream> #include <cstring> #include <algo

[JSOI2007]文本生成器 [AC自动机,dp]

时刻要记住正难则反,可以知道总数是 \(26^m\),我们可以减掉不合法的. AC自动机上面dp,不合法的显然就是没有出现任意的一个串,根据rainy的教导 单词 \(b,bce,abcd\) 的 ACAM 然后 \(dp\) 就好了,由于点数不超过 \(n*m \leq 6000\),然后你每一位枚举复杂度是 \(m^2n\) 的 可以通过本题 // powered by c++11 // by Isaunoya #include <bits/stdc++.h> #define rep(i,

POJ 3691 DNA repair 基于AC自动机的DP

dp[i][j] 表示长度为 i 的前缀到达第 j 个节点的最小更改数目. 很显然有dp[0][0] = 0; dp[ i ][ j ] = min(dp[ i ][ j ],dp[i-1][k] + (j == k ? 0 : 1)),当且仅当j,k满足下列条件时. j 不为某条模式串的末节点 且 j 到 root 的由失败指针组成的路径上无末节点. j 是k的儿子节点 或者 j 的父节点可由 k 沿着失败指针找到. #include <algorithm> #include <ios

[AC自动机+概率dp] hdu 3689 Infinite monkey theorem

题意: 给n个字母,和m次数. 然后输入n个字母出现的概率 然后再给一个目标串str 然后问m次中敲出目标串的概率是多少. 思路: AC自动机+概率dp的简单题. 首先建立trie图,然后就是状态转移了 dp版本: dp三重循环变量次数,节点数,和字母数 代码: #include"cstdlib" #include"cstdio" #include"cstring" #include"cmath" #include"

HDU 2825 Wireless Password (AC自动机,DP)

http://acm.hdu.edu.cn/showproblem.php?pid=2825 Wireless Password Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 4560    Accepted Submission(s): 1381 Problem Description Liyuan lives in a old a