题目大意:
现在已知某wifi密码只包含小写字母,长度为n (1 <= n <= 25), 现在给出一个词典包含m个单词,问存在多少种长度为n的序列其包含m个单词中的至少k个,其中对于重叠部分也算包含,比如序列shee 包含单词she和he
结果对 20090717 取模
大致思路:
首先不难发现可疑用AC自动机建立状态转移图,next [ now ] 表示在节点now处发生失配之后的去向,那么如果用 dp[ i ][ j ][ k ]表示长度为 i ,终止节点为 j ,包含状态为 k 的串友多少种,用end[ now ]表示节点now是否是单词节点, 其值为 1 << id, id为单词编号,那么根据状态压缩的思想,不难发现 dp[ i ][ j ][ k ]的下一个状态是:
对于下一个字母编号 t (0 <= t < 26) dp[ i + 1][ next[ j ][ t ] ][ k | end[ next[ j ][ t ] ] ] += dp[ i ] [ j ][ k ];
这个状态转移方程由于 i 的单调性可以用循环简单实现,初始时 dp[ 0 ][ 0 ][ 0 ] = 1, 其他都是0
此题还需要注意很多细节来优化, 不然还是会TLE, 优化见代码细节
代码如下:
Result : Accepted Memory : 6784 KB Time : 546 ms
/*
* Author: Gatevin
* Created Time: 2014/11/20 16:53:49
* File Name: Kagome.cpp
*/
#include<iostream>
#include<sstream>
#include<fstream>
#include<vector>
#include<list>
#include<deque>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<bitset>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<cmath>
#include<ctime>
#include<iomanip>
using namespace std;
const double eps(1e-8);
typedef long long lint;
int n, m, w;
int dp[30][110][1 << 10];
const int mod = 20090717;
int num[1 << 10];
void init()//预先处理状态中含有单词数量,避免重复计算
{
memset(num, 0, sizeof(num));
for(int i = 0; i < (1 << 10); i++)
{
int tmp = i;
while(tmp)
{
if(tmp & 1) num[i]++;
tmp >>= 1;
}
}
return;
}
struct Trie
{
int next[110][26], fail[110], end[110];
int L, root;
int newnode()
{
for(int i = 0; i < 26; i++)
next[L][i] = -1;
end[L++] = 0;
return L - 1;
}
void init()
{
L = 0;
root = newnode();
}
void insert(char *s, int id)
{
int now = root;
for(; *s; s++)
{
if(next[now][*s - 'a'] == -1)
next[now][*s - 'a'] = newnode();
now = next[now][*s - 'a'];
}
end[now] = (1 << id);
return;
}
void build()
{
queue <int> Q;
fail[root] = root;
Q.push(root);
while(!Q.empty())
{
int now = Q.front();
Q.pop();
if(end[fail[now]]) end[now] |= end[fail[now]];
for(int i = 0; i < 26; i++)
if(next[now][i] == -1)
next[now][i] = now == root ? root : next[fail[now]][i];
else
{
fail[next[now][i]] = now == root ? root : next[fail[now]][i];
Q.push(next[now][i]);
}
}
return;
}
void solve()
{
//memset(dp, 0, sizeof(dp)); 由于dp数组较大,每次都全部清空耗时多
for(int i = 0; i <= n; i++)
for(int j = 0; j < L; j++)
for(int p = 0; p < (1 << m); p++)
dp[i][j][p] = 0;
dp[0][0][0] = 1;
for(int i = 0; i < n; i++)
for(int j = 0; j < L; j++)
for(int k = 0; k < (1 << m); k++)
if(dp[i][j][k] > 0)//避免不必要的计算
for(int t = 0; t < 26; t++)
dp[i + 1][next[j][t]][k | end[next[j][t]]] = (dp[i + 1][next[j][t]][k | end[next[j][t]]] + dp[i][j][k]) % mod;
int ans = 0;
for(int k = 0; k < (1 << m); k++)
if(num[k] >= w)//将i的循环放在里面比将ki放在外层循环减少了很多不必要的判断
for(int i = 0; i < L; i++)
ans = (ans + dp[n][i][k]) % mod;
printf("%d\n", ans);
return;
}
};
Trie AC;
int main()
{
char ts[11];
init();
while(scanf("%d %d %d", &n, &m, &w), n || m || w)
{
AC.init();
for(int i = 0; i < m; i++)
{
scanf("%s", ts);
AC.insert(ts, i);
}
AC.build();
AC.solve();
}
return 0;
}