文章目录
前言
写了一下AC自动机的题目,发现蛮喜欢出AC自动机+dp的题目。而且dp状态通常为 d p [ i ] [ j ] dp[i][j] dp[i][j]表示已经串长 i i i,然后匹配到了自动机的 j j j节点。这里写一下总结
P4052 [JSOI2007]文本生成器
题目链接:[JSOI2007]文本生成器
题目大意:给定
n
n
n个单词,求出有多少个长
m
m
m的文章包含至少一个单词。
数据范围:
1
≤
n
≤
60
,
1
≤
m
≤
100
,
1
≤
∣
s
i
∣
≤
100
。
1≤n≤60,1 \leq m \le100 ,1≤|s_i|≤100。
1≤n≤60,1≤m≤100,1≤∣si∣≤100。
题解:AC自动机+dp。这道题在去年还是个蒟蒻的时候就已经放入我的收藏里面了,说的好像现在不是蒟蒻一样 。不过一直没去写(主要是太菜了)。我们来分析一下,首先很容易的想到容斥一下
a
n
s
=
至
少
一
个
s
i
→
2
6
m
−
(
一
个
都
不
包
含
)
ans=至少一个s_i\to 26^m-(一个都不包含)
ans=至少一个si→26m−(一个都不包含)现在问题变成如何求出不包含
s
i
s_i
si的串数量。考虑
d
p
dp
dp,
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]表示已经串长
i
i
i,然后匹配到了自动机的
j
j
j节点的串个数。想进行
d
p
dp
dp的转移还需要解决一个问题,就是有些节点是不能到达的,因为符号要求的串是不能包含
s
i
s_i
si的,所以我们需要预处理出来,这个我们在
b
u
i
l
d
build
build函数中解决。剩下的就是递推了。具体看代码吧。
AC代码:
#include<bits/stdc++.h>
#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
T res = 0, f = 1; char c = getchar();
while (!isdigit(c)) {
if (c == '-')f = -1; c = getchar();
}
while (isdigit(c)) {
res = (res << 3) + (res << 1) + c - '0'; c = getchar();
}
x = res * f;
}
#define int long long
const ll N = 10000 + 10;
const int mod = 1e4 + 7;
int nxt[N][26],tot,pd[N],fail[N],dp[105][N];
int n,m;
void insert(char* s)
{
int n = strlen(s);
int now = 0;
for (int i = 0; i < n; i++)
{
if (!nxt[now][s[i] - 'A'])
nxt[now][s[i] - 'A'] = ++tot;
now=nxt[now][s[i] - 'A'];
}
pd[now] = 1;
}
void build()
{
queue<int>pls;
for (int i = 0; i < 26; i++)if (nxt[0][i])pls.push(nxt[0][i]), fail[nxt[0][i]] = 0;
while (pls.size())
{
int f = pls.front(); pls.pop();
for (int i = 0; i < 26; i++)
{
if (nxt[f][i])
{
fail[nxt[f][i]] = nxt[fail[f]][i];
pd[nxt[f][i]] |= pd[nxt[fail[f]][i]];
pls.push(nxt[f][i]);
}
else
nxt[f][i] = nxt[fail[f]][i];
}
}
}
char s[N];
int fpow(int x, int y)
{
int ans = 1;
while (y)
{
if (y & 1)ans = ans * x % mod;
x = x * x % mod; y >>= 1;
}
return ans;
}
signed main()
{
//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
read(n), read(m);
for (int i = 1; i <= n; i++)
{
scanf("%s", s); insert(s);
}
build();
dp[0][0] = 1;//dp[i][j]表示已经长i,匹配到了j节点
for (int i = 1; i <= m; i++)
{
for (int j = 0; j <= tot; j++)
{
for (int nx = 0; nx < 26; nx++)
if (!pd[nxt[j][nx]])
dp[i][nxt[j][nx]] = (dp[i][nxt[j][nx]] + dp[i - 1][j]) % mod;
}
}
int ans = fpow(26, m);
for (int j = 0; j <= tot; j++)ans = (ans - dp[m][j]) % mod;
printf("%lld\n", (ans % mod + mod) % mod);
return 0;
}
P3041 [USACO12JAN]Video Game G
题目链接:[USACO12JAN]Video Game G
题目大意:给定
n
n
n个单词,求出有多少个长
k
k
k的文章最大匹配单词多少次。
数据范围:
1
≤
n
≤
20
,
1
≤
k
≤
1
0
3
,
1
≤
∣
s
i
∣
≤
15
1≤n≤20,1 \leq k \leq 10^3,1≤∣s_i∣≤15
1≤n≤20,1≤k≤103,1≤∣si∣≤15
题解:AC自动机+dp,和上面一题是一样的,只不过不用处理那些点不能转移到。然后
d
p
dp
dp变成求
m
a
x
max
max。其实
d
p
dp
dp就两个问题,
d
p
dp
dp数组的初始化以及状态方程的转移。
AC代码:
#include<bits/stdc++.h>
#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
T res = 0, f = 1; char c = getchar();
while (!isdigit(c)) {
if (c == '-')f = -1; c = getchar();
}
while (isdigit(c)) {
res = (res << 3) + (res << 1) + c - '0'; c = getchar();
}
x = res * f;
}
#define int long long
const ll N = 1000 + 10;
const int mod = 1e9 + 7;
int n, k;
int nxt[N][3], fail[N], val[N],tot;
void insert(char* s)
{
int now = 0;
int n = strlen(s);
for (int i = 0; i < n; i++)
{
if (!nxt[now][s[i] - 'A'])nxt[now][s[i] - 'A'] = ++tot;
now = nxt[now][s[i] - 'A'];
}
val[now]++;
}
void build()
{
queue<int>pls;
for (int i = 0; i < 3; i++)if (nxt[0][i])pls.push(nxt[0][i]);
while (pls.size())
{
int f = pls.front(); pls.pop();
for (int i = 0; i < 3; i++)
{
if (nxt[f][i])
{
fail[nxt[f][i]] = nxt[fail[f]][i];
val[nxt[f][i]] += val[nxt[fail[f]][i]];
pls.push(nxt[f][i]);
}
else
nxt[f][i] = nxt[fail[f]][i];
}
}
}
char s[N];
int dp[1005][N];
signed main()
{
//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
read(n), read(k);
for (int i = 1; i <= n; i++)
{
scanf("%s", s); insert(s);
}
build();
memset(dp, -0x3f, sizeof(dp));
dp[0][0] = 0;
for (int i = 1; i <= k; i++)
{
for (int j = 0; j <= tot; j++)
{
for (int nx = 0; nx < 3; nx++)
{
dp[i][nxt[j][nx]] =max(dp[i][nxt[j][nx]], dp[i - 1][j] + val[nxt[j][nx]]);
}
}
}
int ans = 0;
for (int j = 0; j <= tot; j++)ans = max(ans, dp[k][j]);
printf("%lld\n", ans);
return 0;
}
P3311 [SDOI2014] 数数
题目链接:[SDOI2014] 数数
题目大意:给定
m
m
m个数字串,求出有不超过
n
n
n的有多少个数不包含给定数字串。
数据范围:
1
≤
n
<
1
0
1201
,
1
≤
m
≤
100
,
1
≤
∑
i
=
1
m
∣
s
i
∣
≤
1500
1≤n<10^{1201},1 \leq m \leq 100,1 \leq \sum_{i = 1}^m |s_i| \leq 1500
1≤n<101201,1≤m≤100,1≤∑i=1m∣si∣≤1500
题解:AC自动机+数位dp。非常好的一道题,细节蛮多的。主要解释一下
d
f
s
dfs
dfs函数,
c
u
r
cur
cur表示当前还剩多少位,
l
i
m
i
t
limit
limit表示是否顶着数位的上界,
p
o
s
pos
pos表示当前在自动机的什么位置,_0表示是否有前导零。具体细节看代码。
AC代码:
#include<bits/stdc++.h>
#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
T res = 0, f = 1; char c = getchar();
while (!isdigit(c)) {
if (c == '-')f = -1; c = getchar();
}
while (isdigit(c)) {
res = (res << 3) + (res << 1) + c - '0'; c = getchar();
}
x = res * f;
}
#define int long long
const ll N = 200000 + 10;
const int mod = 1e9 + 7;
int nxt[N][10], fail[N],tot, pd[N];
void insert(char* s)
{
int now = 0, n = strlen(s);
for (int i = 0; i < n; i++)
{
if (!nxt[now][s[i] - '0'])
nxt[now][s[i] - '0'] = ++tot;
now = nxt[now][s[i] - '0'];
}
pd[now] = 1;
}
void build()
{
queue<int>pls;
for (int i = 0; i < 10; i++)if (nxt[0][i])pls.push(nxt[0][i]);
while (pls.size())
{
int f = pls.front(); pls.pop();
for (int i = 0; i < 10; i++)
{
if (nxt[f][i])
{
fail[nxt[f][i]] = nxt[fail[f]][i];
pd[nxt[f][i]] |= pd[nxt[fail[f]][i]];//打上标记表示不能到达
pls.push(nxt[f][i]);
}
else
nxt[f][i] = nxt[fail[f]][i];
}
}
}
int dp[1205][1505],w[1205],m;
int dfs(int cur, int limit, int pos,int _0)
{
if (!cur)return 1;
if (!limit&&!_0 && ~dp[cur][pos])return dp[cur][pos];
int tp = limit ? w[cur]:9;
int ans = 0;
for (int i = 0; i <= tp; i++)
{
if (!pd[nxt[pos][i]]||(i==0&&_0))//可以去
{
ans = (ans + dfs(cur - 1, limit && i == tp, (i == 0 && _0) ? 0 : nxt[pos][i], (i == 0 && _0))) % mod;
}
}
if (!limit && !_0)dp[cur][pos] = ans;
return (ans % mod + mod)%mod;
}
char s[N];
signed main()
{
//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
memset(dp, -1, sizeof(dp));
scanf("%s", s + 1);
w[0] = strlen(s + 1);
reverse(s + 1, s + 1 + w[0]);
for (int i = 1; i <= w[0]; i++)w[i] = s[i] - '0';
read(m);
for (int i = 1; i <= m; i++)
{
scanf("%s", s + 1);
insert(s + 1);
}
build();
printf("%lld\n",dfs(w[0], 1, 0, 1)-1);
return 0;
}
P5231 [JSOI2012]玄武密码
题目链接: [JSOI2012]玄武密码
题目大意:给定一个母串
S
S
S,给出
m
m
m段文字,求每一段文字中最长的一段前缀满足是
S
S
S中的子串。
数据范围:
1
≤
n
≤
1
e
7
,
1
≤
m
≤
1
e
5
,
1
≤
∣
s
i
∣
≤
100
1\le n\le 1e7,1\le m\le 1e5,1\le |s_i|\le 100
1≤n≤1e7,1≤m≤1e5,1≤∣si∣≤100
题解:AC自动机,我们将m段文字建成AC自动机,然后将母串放上去进行匹配,查询的时候只要将看文字可以到达的最远的已匹配点。
AC代码:
#include<bits/stdc++.h>
#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
T res = 0, f = 1; char c = getchar();
while (!isdigit(c)) {
if (c == '-')f = -1; c = getchar();
}
while (isdigit(c)) {
res = (res << 3) + (res << 1) + c - '0'; c = getchar();
}
x = res * f;
}
const ll N = 10000000 + 10;
const int mod = 1e9 + 7;
int num(char c)
{
if (c == 'E')return 0;
if (c == 'S')return 1;
if (c == 'W')return 2;
return 3;
}
int n, m;
char s[N];
char a[100005][105];
int nxt[N][4],vis[N],fail[N],tot;
void insert(char* s)
{
int n = strlen(s); int now = 0;
for (int i = 0; i < n; i++)
{
if (!nxt[now][num(s[i])])nxt[now][num(s[i])] = ++tot;
now = nxt[now][num(s[i])];
}
}
void build()
{
queue<int>pls;
for (int i = 0; i < 4; i++)if (nxt[0][i])pls.push(nxt[0][i]);
while (pls.size())
{
int f = pls.front(); pls.pop();
for (int i = 0; i < 4; i++)
{
if (nxt[f][i])
{
fail[nxt[f][i]] = nxt[fail[f]][i];
pls.push(nxt[f][i]);
}
else
nxt[f][i] = nxt[fail[f]][i];
}
}
}
vector<int>p[N];
void dfs(int u)
{
for (auto to : p[u])dfs(to), vis[u] |= vis[to];
}
int query(char* s)
{
int ans = 0; int now = 0, n = strlen(s);
for (int i = 0; i < n; i++)
{
now = nxt[now][num(s[i])];
if (vis[now])ans = max(ans, i + 1);
}
return ans;
}
int main()
{
//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
read(n); read(m);
scanf("%s", s + 1);
for (int i = 1; i <= m; i++)
{
scanf("%s", a[i] + 1); insert(a[i] + 1);
}
build();
int now = 0;
for (int i = 1; i <= n; i++)
{
now = nxt[now][num(s[i])]; vis[now] = 1;
}
for (int i = 1; i <= tot; i++)p[fail[i]].push_back(i);
dfs(0);
for (int i = 1; i <= m; i++)printf("%d\n", query(a[i] + 1));
return 0;
}
POJ-DNA Sequence
题目链接:DNA Sequence
题目大意:给定
m
m
m个模板串,求长
n
n
n的串中不含模板串有多少个。
数据范围:
0
≤
m
≤
10
,
1
≤
n
≤
2
e
9
,
1
≤
∣
s
i
∣
≤
10
0\le m\le 10,1\le n\le2e9,1\le|s_i|\le10
0≤m≤10,1≤n≤2e9,1≤∣si∣≤10
题解:AC自动机+矩阵优化DP。很经典的题,用来锻炼一下矩阵和自动机。令
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]表示长
i
i
i在自动机节点
j
j
j上合法的串数量。然后我们能发现
j
j
j的范围不会超过
100
100
100,所以考虑用矩阵来优化递推。时间复杂度
O
(
(
∑
∣
s
i
∣
)
3
∗
l
o
g
n
)
O((\sum{|s_i|})^3*logn)
O((∑∣si∣)3∗logn)。
AC代码:
#include<vector>
#include<set>
#include<map>
#include<stack>
#include<queue>
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cmath>
#include<time.h>
#include<stdio.h>
#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
T res = 0, f = 1; char c = getchar();
while (!isdigit(c)) {
if (c == '-')f = -1; c = getchar();
}
while (isdigit(c)) {
res = (res << 3) + (res << 1) + c - '0'; c = getchar();
}
x = res * f;
}
const ll N = 200000 + 10;
const int mod = 100000;
struct mat
{
int n, m;
int a[105][105];
mat(int x, int y)
{
n = x, m = y;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
a[i][j] = 0;
}
mat()
{
n = 0, m = 0;
}
mat operator*(const mat& b)const
{
mat c(n, b.m);
for (int i = 1; i <= c.n; i++)
for (int j = 1; j <= c.m; j++)
for (int k = 1; k <= m; k++)
c.a[i][j] = (c.a[i][j] + 1ll * a[i][k] * b.a[k][j] % mod) % mod;
return c;
}
void init()
{
for (int i = 1; i <= n; i++)a[i][i] = 1;
}
mat operator^(int y)const
{
mat x = *this;
mat ans(n, m); ans.init();
while (y)
{
if (y & 1)ans = ans * x;
x = x * x; y >>= 1;
}
return ans;
}
}base,ans;
int get(char c)
{
if (c == 'A')return 0;
if (c == 'C')return 1;
if (c == 'T')return 2;
return 3;
}
int tot = 1;
struct ACAM
{
int nxt[N][4], fail[N], vis[N];
void insert(char* s)
{
int n = strlen(s);
int now = 1;
for (int i = 0; i < n; i++)
{
if (!nxt[now][get(s[i])])nxt[now][get(s[i])] = ++tot;
now = nxt[now][get(s[i])];
}
vis[now] = 1;//不能达到
}
void build()
{
queue<int>pls;
for (int i = 0; i < 4; i++)
{
if (nxt[1][i])pls.push(nxt[1][i]), fail[nxt[1][i]] = 1;
else
nxt[1][i] = 1;
}
while (pls.size())
{
int f = pls.front(); pls.pop();
for (int i = 0; i < 4; i++)
{
if (nxt[f][i])
{
fail[nxt[f][i]] = nxt[fail[f]][i];
vis[nxt[f][i]] |= vis[nxt[fail[f]][i]];
pls.push(nxt[f][i]);
}
else
nxt[f][i] = nxt[fail[f]][i];
}
}
}
}AC;
int n, m;
char s[N];
mat init(int x)
{
mat ans(x, x);
for (int i = 1; i <= x; i++)
{
if (AC.vis[i])continue;
for (int nx = 0; nx < 4; nx++)
{
int to = AC.nxt[i][nx];
if (AC.vis[to])continue;
ans.a[i][to]++;
}
}
return ans;
}
int main()
{
//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
read(m), read(n);
for (int i = 1; i <= m; i++)
{
scanf("%s", s + 1); AC.insert(s + 1);
}
AC.build();
base = init(tot);
base = base ^ n;
mat ans(tot, tot);
ans.a[1][1] = 1;
ans = ans * base;
ll sum = 0;
for (int i = 1; i <= tot; i++)sum = (sum + ans.a[1][i]) % mod;
printf("%lld\n", sum % mod);
return 0;
}
POJ太慢了。这份代码是究极卡过去的。
HDU - 2243 考研路茫茫――单词情结
题目链接:HDU - 2243
题意:给定n个单词,求不超过
L
L
L其中至少包含一个单词的串数量。
数据范围:
∑
∣
s
i
∣
≤
30
,
L
≤
2
31
\sum|s_i|\le30,L\le2^{31}
∑∣si∣≤30,L≤231
题解:AC自动机+矩阵优化DP。令
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]表示长
i
i
i在自动机节点
j
j
j一个单词不含的串数量。
a
n
s
=
∑
i
=
1
L
2
6
i
−
∑
i
=
1
i
=
L
∑
j
≤
t
o
t
d
p
[
i
]
[
j
]
ans=\sum_{i=1}^{L}26^i-\sum_{i=1}^{i=L}\sum_{j\le tot} dp[i][j]
ans=i=1∑L26i−i=1∑i=Lj≤tot∑dp[i][j]这个式子的两项都需要用矩阵运算来计算。对于需要统计过程所有的值的和的题目,我们将矩阵扩充一列,全放上
1
1
1。
AC代码:
#include<bits/stdc++.h>
#define ld long double
#define ll long long
#define ull unsigned long long
using namespace std;
template<class T>
void read(T& x)
{
T res = 0, f = 1; char c = getchar();
while (!isdigit(c)) {
if (c == '-')f = -1; c = getchar();
}
while (isdigit(c)) {
res = (res << 3) + (res << 1) + c - '0'; c = getchar();
}
x = res * f;
}
const ll N = 200 + 10;
const int mod = 1e9 + 7;
struct mat
{
ull a[32][32];
ull n, m;
mat(int x, int y)
{
n = x, m = y;
memset(a, 0, sizeof(a));
}
mat(){}
void init()
{
for (int i = 1; i <= n; i++)a[i][i] = 1;
}
mat operator *(const mat& b)
{
mat c(n, b.m);
for (int i = 1; i <= c.n; i++)
for (int j = 1; j <= c.m; j++)
for (int k = 1; k <= m; k++)
c.a[i][j] += a[i][k] * b.a[k][j];
return c;
}
mat operator^(int y)
{
mat ans(n, n); ans.init();
mat x = *this;
while (y)
{
if (y & 1)ans = ans * x;
x = x * x; y >>= 1;
}
return ans;
}
}base;
int nxt[50][26], tot = 1, fail[N],vis[N];
void insert(char* s)
{
int n = strlen(s);
int now = 1;
for (int i = 0; i < n; i++)
{
if (!nxt[now][s[i] - 'a'])nxt[now][s[i] - 'a'] = ++tot;
now = nxt[now][s[i] - 'a'];
}
vis[now] = 1;
}
void build()
{
queue<int>pls;
for (int i = 0; i < 26; i++)
if (nxt[1][i])pls.push(nxt[1][i]), fail[nxt[1][i]] = 1;
else nxt[1][i] = 1;
while (pls.size())
{
int f = pls.front(); pls.pop();
for (int i = 0; i < 26; i++)
{
if (nxt[f][i])
{
fail[nxt[f][i]]= nxt[fail[f]][i];;
vis[nxt[f][i]] |= vis[fail[nxt[f][i]]];
pls.push(nxt[f][i]);
}
else
nxt[f][i] = nxt[fail[f]][i];
}
}
}
void init()
{
memset(nxt, 0, sizeof(nxt));
memset(vis, 0, sizeof(vis));
memset(fail, 0, sizeof(fail));
tot = 1;
}
mat getmat(int x)
{
mat ans(x, x);
for (int i = 1; i <= x-1; i++)
{
if (vis[i])continue;
for (int j = 0; j < 26; j++)
{
int to = nxt[i][j];
if (vis[to])continue;
ans.a[i][to]++;
}
}
for (int i = 1; i <= x; i++)ans.a[i][x]=1;
return ans;
}
int n, L;
char s[N];
ull calcsum(ll x)
{
mat t(2, 2);
t.a[1][1] = 26, t.a[1][2] = t.a[2][2] = 1;
t = t ^ (x);
return t.a[1][1]+t.a[1][2];
}
int main()
{
//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
while (scanf("%d%d", &n, &L) != EOF)
{
init();
for (int i = 1; i <= n; i++)scanf("%s", s + 1), insert(s + 1);
build();
base = getmat(tot+1);
mat ans(tot+1, tot+1);
ans.a[1][1] = 1;
base = (base ^ (L));
ans = ans*base;
ull nans = calcsum(L);
//nans -= ans.a[1][tot + 1];
for (int i = 1; i <= tot+1; i++)nans -= ans.a[1][i];
printf("%llu\n", nans);
}
return 0;
}
矩阵是真的难写难调,来自一个调了2h的菜鸡
G - Wireless Password
题目链接: HDU - 2825
题意:给定
m
m
m个单词,求长
n
n
n并且包含至少
k
k
k个单词的串数量
数据范围:
1
≤
n
≤
25
,
0
≤
m
≤
10
,
k
≤
m
1\le n \le 25,0\le m\le 10,k\le m
1≤n≤25,0≤m≤10,k≤m
题解:AC自动机+状压
d
p
dp
dp。
d
p
[
i
]
[
j
]
[
s
t
]
dp[i][j][st]
dp[i][j][st]表示长
i
i
i到达自动机
j
j
j节点时状态为
s
t
st
st的串数量。
a
n
s
=
∑
j
=
1
t
o
t
∑
s
t
d
p
[
n
]
[
j
]
[
s
t
]
ans=\sum_{j=1}^{tot}\sum_{st} dp[n][j][st]
ans=j=1∑totst∑dp[n][j][st]
AC代码:
#include<bits/stdc++.h>
#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
T res = 0, f = 1; char c = getchar();
while (!isdigit(c)) {
if (c == '-')f = -1; c = getchar();
}
while (isdigit(c)) {
res = (res << 3) + (res << 1) + c - '0'; c = getchar();
}
x = res * f;
}
const ll N = 25 + 10;
const int mod = 20090717;
int nxt[N*10][26],fail[N * 10],st[N * 10],tot=1;
void insert(char* s,int idt)
{
int n = strlen(s); int now = 1;
for (int i = 0; i < n; i++)
{
if (!nxt[now][s[i] - 'a'])nxt[now][s[i] - 'a'] = ++tot;
now = nxt[now][s[i] - 'a'];
}
st[now] |= 1 << (idt - 1);
}
void build()
{
queue<int>pls;
for (int i = 0; i < 26; i++)
{
if (nxt[1][i])pls.push(nxt[1][i]),fail[nxt[1][i]]=1;
else
nxt[1][i] = 1;
}
while (pls.size())
{
int f = pls.front(); pls.pop();
for (int i = 0; i < 26; i++)
{
if (nxt[f][i])
{
fail[nxt[f][i]] = nxt[fail[f]][i];
pls.push(nxt[f][i]);
st[nxt[f][i]] |= st[fail[nxt[f][i]]];
}
else
nxt[f][i] = nxt[fail[f]][i];
}
}
}
int n, m, k, dp[N][105][(1 << 10)+10];
char s[N];
void init()
{
memset(dp, 0, sizeof(dp));
for (int i = 1; i <= tot; i++)memset(nxt[i], 0, sizeof(nxt[i])),fail[i]=0,st[i]=0;
tot = 1;
}
int calc(int x)
{
int ans = 0;
while (x)
{
x -= (x & -x); ans++;
}
return ans;
}
int main()
{
//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
while (scanf("%d%d%d", &n, &m, &k) != EOF)
{
if (!n && !m && !k)break;
init();
for (int i = 1; i <= m; i++)
{
scanf("%s", s + 1); insert(s + 1, i);
}
build();
int mx = (1 << m) - 1;
dp[0][1][0] = 1;
for(int i=0;i<=n-1;i++)
for (int j = 1; j <= tot; j++)
{
for (int nt = 0; nt <= mx; nt++)
{
if (!dp[i][j][nt])continue;
for (int nx = 0; nx < 26; nx++)
{
int to = nxt[j][nx];
dp[i + 1][to][st[to] | nt] = (dp[i + 1][to][st[to] | nt] + dp[i][j][nt]) % mod;
}
}
}
int ans = 0;
for (int i = 1; i <= tot; i++)
for (int j = 0; j <= mx; j++)
if (calc(j) >= k)ans = (ans + dp[n][i][j])%mod;
printf("%d\n", ans);
}
return 0;
}