BZOJ1030: [JSOI2007]文本生成器 AC自动机 + DP

正着难想,要处理如何容斥,那就反着来
f[i][j] f [ i ] [ j ] 为当前为随机串的第i位 匹配AC自动机里编号为j的点
那么答案就是 26mf[m][j] 26 m − ∑ f [ m ] [ j ]
写了快速幂甚至比没写还慢了一丢丢……

/**************************************************************
    Problem: 1030
    User: YJMSTR
    Language: C++
    Result: Accepted
    Time:92 ms
    Memory:3852 kb
****************************************************************/

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define Max(_A,_B) (_A>_B?_A:_B)
#define Min(_A,_B) (_A<_B?_A:_B)
int read(){
    int s = 0;bool f = 1;char c = getchar();
    while(c > '9' || c < '0'){if(c == '-') f = 0; c = getchar();}
    while(c >= '0' && c <= '9') {
    s = s * 10 + c - '0';
    c = getchar();
    }
    return f == 1 ? s : -s;
}
const int maxn = 6001, maxc = 26, md = 10007;
char s[101];
/*
统计答案:同时出现多个字符串 要容斥
f[i][j]:在Trie图上走i步,匹配第j个结点的方案数
f[i][j] -> f[i + 1][son[j]]
*/
int f[101][6001], n, m, a, b;
struct AC_Automaton {
    int ch[maxn][maxc], fail[maxn], q[maxn], qh, qt, tot;
    bool leaf[maxn];
    void init(){
        memset(ch, -1, sizeof(ch));
        memset(fail, 0 ,sizeof(fail));
        memset(leaf, 0, sizeof(leaf));
        tot = 0;
    }
    void insert(char *str){
        int p = 0;
        for(int i = 0; str[i]; ++i) {
            if(ch[p][str[i] - 'A'] == -1)  ch[p][str[i] - 'A'] = ++tot;
            p = ch[p][str[i] - 'A'];
        }
        leaf[p] = true;//叶子结点
    }
    void build(){
        qh = qt = 0;
        for(int i = 0; i < maxc; i++) {
            if(ch[0][i] == -1) ch[0][i] = 0;
            else q[qt++] = ch[0][i];
        }
        while(qh < qt) {
            int p = q[qh++];
            for(int i = 0; i < maxc; i++) {
                if(ch[p][i] == -1) ch[p][i] = ch[fail[p]][i];
                else {
                    fail[ch[p][i]] = ch[fail[p]][i];
                    leaf[ch[p][i]] |= leaf[fail[ch[p][i]]];
                    q[qt++] = ch[p][i];
                }
            }
        }
    }
    void dp(){
    //枚举ac自动机上的所有结点,叶子结点都是合法的
    //正难则反,记数所有不合法状态种数拿去减
        f[0][0] = b = 1;
        for(int i = 1; i <= m; i++) {
            for(int j = 0; j <= tot; j++) {
                if(leaf[j] || !f[i - 1][j]) continue;
                for(int k = 0; k < maxc; k++) 
                    f[i][ch[j][k]] = (f[i][ch[j][k]] + f[i - 1][j]) % md;
            }
        }
        for(int i = 0; i <= tot; i++) if(!leaf[i]) a = (a + f[m][i]) % md;
        for(int i = 1; i <= m; i++) b = (b * maxc) % md;//总方案数,不用快速幂了
    }
}ac;
int main(){
    n = read(), m = read();
    ac.init();
    for(int i = 1; i <= n; i++) {
        scanf("%s", s);
        ac.insert(s);
    }
    ac.build();
    ac.dp();
    printf("%d\n", (b - a + md) % md);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值