【JSOI2007】文本生成器 题解(AC自动机+动态规划)

题目链接

题目大意:给定$n$个子串,要求构造一个长度为$m$的母串使得至少有一个子串是其子串。问方案数。

------------------------

我们可以对要求进行转化:求出不合法的方案数,总方案数减去不合法的方案数即为合法方案数。

首先建一个AC自动机,对于每个串的末尾结点及其$fail$边指向的结点都打上标记,表示遍历AC自动机的时候不经过这些点(因为如果一个串是另一个串的后缀,显然这两个串都是合法的)。

然后就可以大力DP了。设$f[i][j]$表示走了$i$步到达$j$结点的方案数。则有转移方程$f[i+1][son(j)]+=f[i][j]$。答案即为$m^{26}-\sum\limits_{i=1}^{cnt} f[m][i]$。

代码:

#include<bits/stdc++.h>
using namespace std;
const int mod=1e4+7;
int n,m,f[103][6003],cnt,sum,ans=1;
string ss;
struct node
{
    int ch[30],end,fail;
}tree[60005];
inline void build(string s)
{
    int l=s.length(),now=0;
    for (int i=0;i<l;i++)
    {
        if (!tree[now].ch[s[i]-'A']) tree[now].ch[s[i]-'A']=++cnt;
        now=tree[now].ch[s[i]-'A'];  
    }
    tree[now].end=1;
}
inline void getfail()
{
    queue<int> q;
    for (int i=0;i<26;i++)
    {
        if (tree[0].ch[i]) tree[0].fail=0,q.push(tree[0].ch[i]);
    }
    while(!q.empty())
    {
        int now=q.front();q.pop();
        for (int i=0;i<26;i++)
        {
            if (!tree[now].ch[i])
            {
                tree[now].ch[i]=tree[tree[now].fail].ch[i];
                continue;
            }
            tree[tree[now].ch[i]].fail=tree[tree[now].fail].ch[i];
            q.push(tree[now].ch[i]);
            tree[tree[now].ch[i]].end|=tree[tree[tree[now].fail].ch[i]].end;
        }
    }
}
int main()
{
    cin>>n>>m;
    for (int i=1;i<=n;i++) cin>>ss,build(ss);
    tree[0].fail=0;
    getfail();
    f[0][0]=1;
    for (int i=1;i<=m;i++)
        for (int j=0;j<=cnt;j++)
            for (int k=0;k<26;k++)
                if (!tree[tree[j].ch[k]].end)
                    f[i][tree[j].ch[k]]+=f[i-1][j],f[i][tree[j].ch[k]]%=mod;
    for (int i=1;i<=m;i++) ans=(ans*26)%mod;
    for (int i=0;i<=cnt;i++) sum+=f[m][i];
    printf("%d",((ans-sum)%mod+mod)%mod);
    return 0; 
}

 

posted @ 2020-07-30 19:09  我亦如此向往  阅读(152)  评论(0编辑  收藏  举报