传送门
题面:
Liyuan lives in a old apartment. One day, he suddenly found that there was a wireless network in the building. Liyuan did not know the password of the network, but he got some important information from his neighbor. He knew the password consists only of lowercase letters 'a'-'z', and he knew the length of the password. Furthermore, he got a magic word set, and his neighbor told him that the password included at least k words of the magic word set (the k words in the password possibly overlapping).
For instance, say that you know that the password is 3 characters long, and the magic word set includes 'she' and 'he'. Then the possible password is only 'she'.
Liyuan wants to know whether the information is enough to reduce the number of possible passwords. To answer this, please help him write a program that determines the number of possible passwords.
Input
There will be several data sets. Each data set will begin with a line with three integers n m k. n is the length of the password (1<=n<=25), m is the number of the words in the magic word set(0<=m<=10), and the number k denotes that the password included at least k words of the magic set. This is followed by m lines, each containing a word of the magic set, each word consists of between 1 and 10 lowercase letters 'a'-'z'. End of input will be marked by a line with n=0 m=0 k=0, which should not be processed.
Output
For each test case, please output the number of possible passwords MOD 20090717.
Sample Input
10 2 2 hello world 4 1 1 icpc 10 0 0 0 0 0
Sample Output
2 1 14195065
题意:
给 m 个单词构成的集合,统计所有长度为 n 的串中,包含至少 k 个单词的方案数。
题目分析:
AC自动机好题,bzoj1010的加强版。
在bzoj1010中,我们要求的是包含至少1个单词的方案数,当时我们的做法是用一个dp去求出不符合条件的答案,并用总方案数-不符合条件的方案数。
但是,在这个题中,倘若我们直接采取bzoj1010的方法去做的话,我们发现,至少包含k个单词的方案数并没有像1个方案数那么好求。因此我们需要进一步分析。考虑到一共最多会有m个单词,即最多一共会有2^m中不同的单词的搭配,因此我们考虑在普通dp的基础上增加一维作为状态压缩。
我们设为:长度为i的字符串,当前位于Trie树上的第j个结点,且当时单词选取的状态的二进制数为k的符合的方案数。故此时不难发现,该状态必定是由前一个结点位置转移过来的,故会有状态转移方程:
最后求出即为答案。
代码:
#include <bits/stdc++.h>
#define maxn 300
using namespace std;
int dp[30][maxn][1<<10];
int num[maxn*10];
char st[20];
int n,m,k;
const int mod=20090717;
struct Trie{//AC自动机模板
int next[maxn][26],fail[maxn],root,id,End[maxn];
int newnode(){
for(int i=0;i<26;i++){
next[id][i]=-1;
}
End[id]=0;
return id++;
}
void init(){
id=0;
root=newnode();
}
void Insert(char *str,int index){
int len=strlen(str);
int now=root;
for(int i=0;i<len;i++){
if(next[now][str[i]-'a']==-1){
next[now][str[i]-'a']=newnode();
}
now=next[now][str[i]-'a'];
}
End[now]|=(1<<index);
}
void build(){
queue<int>que;
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 now=que.front();
que.pop();
End[now]|=End[fail[now]];
for(int i=0;i<26;i++){
if(next[now][i]==-1){
next[now][i]=next[fail[now]][i];
}
else{
fail[next[now][i]]=next[fail[now]][i];
que.push(next[now][i]);
}
}
}
}
}ac;
void get_num(){//获取每个数中的1的个数
for(int i=0;i<(1<<10);i++){
num[i]=0;
for(int j=0;j<10;j++){
if(i&(1<<j)){
num[i]++;
}
}
}
}
void solve(){
for(int i=0;i<=n;i++){
for(int j=0;j<ac.id;j++){
for(int t=0;t<(1<<m);t++) dp[i][j][t]=0;
}
}
dp[0][0][0]=1;
for(int i=1;i<=n;i++){//dp状态转移
for(int j=0;j<ac.id;j++){
for(int l=0;l<(1<<m);l++){
if(!dp[i-1][j][l]) continue;
for(int x=0;x<26;x++){
int nextj=ac.next[j][x];
int tmp=(l|ac.End[nextj]);
dp[i][ac.next[j][x]][tmp]+=dp[i-1][j][l];
dp[i][ac.next[j][x]][tmp]%=mod;
}
}
}
}
int res=0;
for(int i=0;i<(1<<m);i++){
if(num[i]<k) continue;
for(int j=0;j<ac.id;j++){
res=(res+dp[n][j][i])%mod;
}
}
printf("%d\n",res);
}
int main()
{
get_num();
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",st);
ac.Insert(st,i);
}
ac.build();
solve();
}
return 0;
}