题目链接:点击打开链接
题目大意:有m个关键字(只含有小写字母),让找出最少含有k个关键字的长度为n的字符串(也只含有小写字母)的个数。
分析:做了这几道AC自动机——DP的题,也发现了这类题的一些规律:都是先按关键字建立一颗tire树,然后把树中的每一个节点看做是一种状态。本题定义dp(i,j,k)为长度为i的以j状态结尾,并且含有关键字个数为状态k时的字符串的个数。对于状态k的理解呢,由于关键字的数量很小(m<=10),我们把每个关键字对于为一个二进制位.
实现代码如下:
#include <cstdio>
#include <iostream>
#include <queue>
using namespace std;
const int mod=20090717;
int n,m,k;
int dp[30][110][1<<10];
int num[5000];
struct node
{
int next[110][26],fail[110],end[110];
int root,size;
int newnode()
{
for(int i=0;i<26;i++) next[size][i]=-1;
end[size++]=0;
return size-1;
}
void init()
{
size=0;
root=newnode();
}
void insert(char *str,int id)
{
int p=root,i=0;
while(str[i])
{
int index=str[i]-'a';
if(next[p][index]==-1)
next[p][index]=newnode();
p=next[p][index];
i++;
}
end[p]|=(1<<id);
}
void build_fail()
{
queue <int> que;
fail[root]=root;
for(int i=0;i<26;i++)
if(next[root][i]==-1)
next[root][i]=root;
else
{
fail[ next[root][i] ]=root;
que.push(next[root][i]);
}
while(!que.empty())
{
int p=que.front();
que.pop();
end[p]|=end[ fail[p] ];
for(int i=0;i<26;i++)
if(next[p][i]==-1)
next[p][i]=next[ fail[p] ][i];
else
{
fail[ next[p][i] ]=next[ fail[p] ][i];
que.push(next[p][i]);
}
}
}
void solve()
{
for(int i=0;i<=n;i++)
for(int j=0;j<size;j++)
for(int k=0;k<(1<<m);k++)
dp[i][j][k]=0;
dp[0][0][0]=1;
for(int i=0;i<n;i++)
for(int j=0;j<size;j++)
for(int k=0;k<(1<<m);k++)
if(dp[i][j][k]>0)
for(int x=0;x<26;x++)
{
int cnt_i=i+1,cnt_j=next[j][x];
int cnt_k=(k|end[cnt_j]);
dp[cnt_i][cnt_j][cnt_k]+=dp[i][j][k];
dp[cnt_i][cnt_j][cnt_k]%=mod;
}
int ans=0;
for(int p=0;p<(1<<m);p++)
{
if(num[p]<k) continue;
for(int i=0;i<size;i++)
ans=(ans+dp[n][i][p])%mod;
}
printf("%d\n",ans);
}
}AC;
int main()
{
char str[20];
for(int i=0;i<(1<<10);i++)
{
num[i]=0;
for(int j=0;j<10;j++)
if(i&(1<<j)) num[i]++;
}
while(scanf("%d%d%d",&n,&m,&k))
{
if(n==0&&m==0&&k==0) break;
AC.init();
for(int i=0;i<m;i++)
{
scanf("%s",str);
AC.insert(str,i);
}
AC.build_fail();
AC.solve();
}
return 0;
}