传送门

AC自动机

考虑一位一位填字符

一旦包含了单词(即经过了结束标记或fail上的结束标记)

那么后面不管填什么都是一篇可读的文章

如果一共要填 m 个单词,当前填到了第 i 个字符就一定可读了

那么后面每个字符都有 26 种填法,所以方案就多了 26^(m-i) 种

然后打了个 30 分的搜索

在AC自动机上走,一旦有匹配就更新答案,不用再下去搜,返回找其他方案

其他TLE

P4052 [JSOI2007]文本生成器_搜索P4052 [JSOI2007]文本生成器_i++_02
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
const int mo=10007;
const int N=1e5+7;
int n,m,ans;
int c[N][27],fail[N],cnt;
bool pd[N];
char a[N];
inline void ins()
{
    int u=0,len=strlen(a);
    for(int i=0;i<len;i++)
    {
        int v=a[i]-'A'+1;
        if(!c[u][v]) c[u][v]=++cnt;
        u=c[u][v];
    }
    pd[u]=1;
}
queue <int> qa;
inline void pre()
{
    for(int i=1;i<=26;i++) if(c[0][i]) qa.push(c[0][i]);
    while(!qa.empty())
    {
        int u=qa.front(); qa.pop();
        for(int i=1;i<=26;i++)
        {
            int v=c[u][i];
            if(!v) c[u][i]=c[fail[u]][i];
            else
            {
                fail[v]=c[fail[u]][i];
                pd[v]|=pd[fail[v]];//只要fail上有结束标记,当前节点也包含可读单词
                qa.push(v);
            }
        }
    }
}
inline int ksm(int x)
{
    int res=1,a=26;
    while(x)
    {
        if(x&1) res=res*a%mo;
        a=a*a%mo; x>>=1;
    }
    return res;
}
void dfs(int stp,int pos)//已经填了stp个单词,当前在自动机的pos位置上
{
    if(pd[pos])//如果包含可读单词直接更新答案并返回
    {
        ans=(ans+ksm(m-stp))%mo;
        return;
    }
    if(stp==m) return;//边界
    for(int i=1;i<=26;i++)
    {
        int v=c[pos][i];
        dfs(stp+1,v);//向下填单词
    }
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        scanf("%s",a);
        ins();
    }
    pre();
    dfs(0,0);
    cout<<ans;
    return 0;
}
30分

考虑把搜索换成DP

设 f[ i ] [ j ] 表示填了第 i 个单词,当前在AC自动机的 j 位置上有几种方案

转移显然,f[ i+1 ] [ k ] += f [ i ] [ j ] ( j 可以走到 k )

如果 j 有包含可读单词,那么 ans += f[ i ] [ j ] * 26^(m-i),并且这个状态不用转移到后面的状态

然后是代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
const int mo=10007;
const int N=1e5+7;
int n,m,ans;
int c[N][27],fail[N],cnt;
bool pd[N];
char a[N];
inline void ins()
{
    int u=0,len=strlen(a);
    for(int i=0;i<len;i++)
    {
        int v=a[i]-'A'+1;
        if(!c[u][v]) c[u][v]=++cnt;
        u=c[u][v];
    }
    pd[u]=1;
}
queue <int> qa;
inline void pre()
{
    for(int i=1;i<=26;i++) if(c[0][i]) qa.push(c[0][i]);
    while(!qa.empty())
    {
        int u=qa.front(); qa.pop();
        for(int i=1;i<=26;i++)
        {
            int v=c[u][i];
            if(!v) c[u][i]=c[fail[u]][i];
            else
            {
                fail[v]=c[fail[u]][i];
                pd[v]|=pd[fail[v]];//只要fail上有结束标记,当前也包含可读单词
                qa.push(v);
            }
        }
    }
}
inline int ksm(int x)
{
    int res=1,a=26;
    while(x)
    {
        if(x&1) res=res*a%mo;
        a=a*a%mo; x>>=1;
    }
    return res;
}
int f[107][N];
void slove()
{
    for(int i=1;i<=26;i++) f[1][c[0][i]]++;//初始状态
    for(int i=1;i<=m;i++)//填第i位
    {
        for(int j=0;j<=cnt;j++)//在自动机上的第j个位置
        {
            if(!f[i][j]) continue;
            if(pd[j])//如果有包含单词
            {
                ans=(ans+f[i][j]*ksm(m-i)%mo)%mo;//更新答案
                continue;
            }
            for(int k=1;k<=26;k++)//负责把状态向下一步转移
            {
                int v=c[j][k];
                f[i+1][v]=(f[i+1][v]+f[i][j])%mo;
            }
        }
    }
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        scanf("%s",a);
        ins();
    }
    pre();
    slove();
    cout<<ans;
    return 0;
}