https://loj.ac/problem/10063
题目描述
给出(N)个单词和文本长度(M),求有多少文本满足其内至少包含一个单词,答案对(10007)取余。
思路
直接求满足的文本比较困难,我们考虑求答案的补集,也就是不包含任何一个单词的文本串的数量。对于这个答案我可以用(dp)求解,但考虑对单词的查找我们需要用(A)C自动机解,因此题目就比较明显了,(AC)自动机上跑(dp),我们用(dp[i][j])表示文本长度为(i),文本的最后一个字符位于(Trie)图上的节点编号为(j)时的方案数。所以转移方程就比较简单了,(dp[i+1][u]+=dp[i][v])((u)能从(v)转移过来),而这个方程的求法与区间(dp)类似,我们第一重枚举长度,因为每一个解必定由更短的长度转移过来。
不过有点必须阐明,我们虽然在(dp)方程中有一维枚举的是(Trie)图上的节点编号,实际还是每一个字母,但有一个类似匹配的过程,因此这个(dp)方程是正确的,它必定不会包含重复的状态,也就可以求出正确答案。
代码
#include<bits/stdc++.h>
using namespace std;
const int mod=10007;
int ch[6100][26],tot=1;
bool ed[6100];
void insert(char *s)
{
int u=1,len=strlen(s);
for(int i=0;i<len;i++)
{
int c=s[i]-'A';
if(!ch[u][c])ch[u][c]=++tot;
u=ch[u][c];
}
ed[u]=1;
}
int qpow(int a,int b)
{
int res=1;
for(;b;b>>=1)
{
if(b&1)res=res*a%mod;
a=a*a%mod;
}
return res;
}
int nxt[6100];
void getfail()
{
for(int i=0;i<26;i++)
ch[0][i]=1;
queue<int>q;
q.push(1);nxt[1]=0;
while(!q.empty())
{
int u=q.front();q.pop();
for(int i=0;i<26;i++)
{
if(!ch[u][i])ch[u][i]=ch[nxt[u]][i];
else
{
int v=nxt[u];
q.push(ch[u][i]);
while(v&&!ch[v][i])v=nxt[v];
nxt[ch[u][i]]=ch[v][i];
}
}
}
for(int i=1;i<=tot;i++)
{
int v=nxt[i];
while(v)ed[i]|=ed[v],v=nxt[v]; //对所有节点的ed标记进行传递
}
}
int dp[110][6010];
char s[110];
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf(" %s",s);
insert(s);
}
getfail();
dp[0][1]=1;
for(int i=0;i<m;i++)
for(int j=1;j<=tot;j++)
for(int k=0;k<26;k++)
if(!ed[j]&&!ed[ch[j][k]])dp[i+1][ch[j][k]]=(dp[i+1][ch[j][k]]+dp[i][j])%mod;//枚举的两个点必定不是单词结尾
int ans=qpow(26,m);
// for(int i=1;i<=tot;i++)
// cout<<dp[m][i]<<endl;
for(int i=1;i<=tot;i++)
ans=(ans-dp[m][i]+mod)%mod;
printf("%d",ans);
}