AC自动机 【JSOI2007】bzoj1030 文本生成器

题目大意:
给出N个由大写字母组成的字符串,再给出一个长度M
求有多少个由大写字母组成的长度为M的字符串满足“至少包含这N个字符串中的一个”
答案对10007取模

题目分析:
直接求满足至少包含N个字符串中一个的字符串个数不好求,那我们可以求一个都不包含的个数,再用总个数(26^M)减去就可以了。
把所有的串扔到AC自动机中,我们避开单词节点做DP;
设f[i][j]代表到第i个字符,是第j个状态的不包含任何给出的字符串的方案数
f[i+1][k]+=f[i][j] (k是j的一个儿子且j与k都不是单词节点);

注意事项:
1、要注意一个字符串包含另一个字符串的情况;
2、DP用循环写,不要用递归(我原来用dfs写的,TLE,很糟糕,当然聪明的读者会有比我更好的方法)
3、f[0][0]赋值为1;
4、最后在模意义下做减法最好加上mod再取余mod。

代码如下:

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cmath>
#include<cstring>
#define N 6060
using namespace std;
const int mod=10007;
int n,m,l,r;
char s[200];
int f[110][N];
struct trie{
    trie *son[26],*fail;
    bool dc;int pos;
    trie()
    {
        memset(son,0,sizeof(son));
        fail=NULL;dc=false;
    }
    void inser(char *s)
    {
        trie *c=this;
        for(int i=0;s[i];i++)
        {
            if(!c->son[s[i]-'A']) c->son[s[i]-'A']=new trie();
            c=c->son[s[i]-'A'];
        }
        c->dc=true;
    }
}*V=new trie,*dl[N];
void Aho_Corasick_Automaton(trie *V)
{
    l=1;r=0;
    V->fail=V;V->pos=0;
    for(int i=0;i<26;i++)
    {
        if(!V->son[i]) V->son[i]=V;
        else {dl[++r]=V->son[i];V->son[i]->fail=V;}
    }
    trie *c,*t;
    while(l<=r)
    {
        c=dl[l++];
        c->pos=l-1;
        for(int i=0;i<26;i++)
        {
            if(!c->son[i]) c->son[i]=c->fail->son[i];
            else
            {
                dl[++r]=c->son[i];
                c->son[i]->fail=c->fail->son[i];
                c->son[i]->dc|=c->fail->son[i]->dc;
            }
        }
    }
    return;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%s",s);
        V->inser(s);
    }
    Aho_Corasick_Automaton(V);
    f[0][0]=1;
    dl[0]=V;
    for(int i=0;i<m;i++)
        for(int j=0;j<=r;j++)
            if(!dl[j]->dc)
                for(int k=0;k<26;k++)
                f[i+1][dl[j]->son[k]->pos]=(f[i+1][dl[j]->son[k]->pos]+f[i][j])%mod;
    int ans=1,a=26,t=m;
    while(t)
    {
        if(t&1) ans=(ans*a)%mod;
        a=(a*a)%mod;
        t>>=1;
    }
    for(int i=0;i<=r;i++)
    {
        if(dl[i]->dc) continue;
        ans=(ans-f[m][dl[i]->pos]+mod)%mod;
    }
    printf("%d\n",ans);
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值