AC自动机简介:KMP是用于解决单模式串匹配问题, AC自动机用于解决多模式串匹配问题。
精华:设这个节点上的字母为C,沿着他父亲的失败指针走,直到走到一个节点,他的儿子中也有字母为C的节点。然后把当前节点的失败指针指向那个字目也为C的儿子。如果一直走到了root都没找到,那就把失败指针指向root。
如果用KMP来解决多模式串匹配问题,则复杂度为O(n + k * m), 而AC自动机的负责度为O(n + m + z), z为模式串出现的次数。
学习链接:
http://hi.baidu.com/nialv7/item/ce1ce015d44a6ba7feded52d
http://blog.csdn.net/niushuai666/article/details/7002823
http://www.cnblogs.com/kuangbin/p/3164106.html
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2222
思路:AC自动机的入门题,用的是bin牛的模板,统计End数组即可,统计过的需要清0.
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 #include <queue> 6 #define FOR(i, a, b) for (int i = (a); i < (b); ++i) 7 #define REP(i, a, b) for (int i = (a); i <= (b); ++i) 8 using namespace std; 9 10 const int MAX_N = (500000 + 500); 11 struct Trie { 12 int next[MAX_N][26], End[MAX_N], fail[MAX_N]; 13 int root, L; 14 int NewNode() 15 { 16 FOR(i, 0, 26) next[L][i] = -1; 17 End[L++] = 0; 18 return L - 1; 19 } 20 void Init() 21 { 22 L = 0; 23 root = NewNode(); 24 } 25 void Insert(char *str) 26 { 27 int len = strlen(str), now = root; 28 FOR(i, 0, len) { 29 int id = str[i] - 'a'; 30 if (next[now][id] == -1) next[now][id] = NewNode(); 31 now = next[now][id]; 32 } 33 ++End[now]; 34 } 35 void Build() 36 { 37 queue<int > que; 38 fail[root] = root; 39 FOR(i, 0, 26) { 40 if (next[root][i] == -1) next[root][i] = root; 41 else { 42 fail[next[root][i]] = root; 43 que.push(next[root][i]); 44 } 45 } 46 while (!que.empty()) { 47 int now = que.front(); 48 que.pop(); 49 FOR(i, 0, 26) { 50 if (next[now][i] == -1) { 51 next[now][i] = next[fail[now]][i]; 52 } else { 53 fail[next[now][i]] = next[fail[now]][i]; 54 que.push(next[now][i]); 55 } 56 } 57 } 58 } 59 int Query(char *str) 60 { 61 int len = strlen(str), now = root, res = 0; 62 FOR(i, 0, len) { 63 int id = str[i] - 'a'; 64 now = next[now][id]; 65 int tmp = now; 66 while (tmp != root) { 67 res += End[tmp]; 68 End[tmp] = 0; 69 tmp = fail[tmp]; 70 } 71 } 72 return res; 73 } 74 } AC; 75 76 int n; 77 char str[1000000 + 100]; 78 79 int main() 80 { 81 int Cas; 82 scanf("%d", &Cas); 83 while (Cas--) { 84 AC.Init(); 85 scanf("%d", &n); 86 REP(i, 1, n) { 87 scanf("%s", str); 88 AC.Insert(str); 89 } 90 AC.Build(); 91 scanf("%s", str); 92 printf("%d\n", AC.Query(str)); 93 } 94 return 0; 95 }
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2896
思路:和上题差不多,只是用End数组来记录序号而已。
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 #include <queue> 6 #include <vector> 7 #define FOR(i, a, b) for (int i = (a); i < (b); ++i) 8 #define REP(i, a, b) for (int i = (a); i <= (b); ++i) 9 using namespace std; 10 11 const int MAX_N = (100000 + 1000); 12 struct Trie { 13 14 int next[MAX_N][128], End[MAX_N], fail[MAX_N]; 15 int root, L; 16 int NewNode() { 17 FOR(i, 0, 128) next[L][i] = -1; 18 End[L++] = 0; 19 return L - 1; 20 } 21 void Init() { 22 L = 0; 23 root = NewNode(); 24 } 25 26 void Insert(char *str, int index) { 27 int len = strlen(str), now = root; 28 FOR(i, 0, len) { 29 int id = str[i]; 30 if (next[now][id] == -1) next[now][id] = NewNode(); 31 now = next[now][id]; 32 } 33 End[now] = index; 34 } 35 void Build() { 36 queue<int > que; 37 fail[root] = root; 38 FOR(i, 0, 128) { 39 if (next[root][i] == -1) next[root][i] = root; 40 else { 41 fail[next[root][i]] = root; 42 que.push(next[root][i]); 43 } 44 } 45 while (!que.empty()) { 46 int now = que.front(); 47 que.pop(); 48 FOR(i, 0, 128) { 49 if (next[now][i] == -1) { 50 next[now][i] = next[fail[now]][i]; 51 } else { 52 fail[next[now][i]] = next[fail[now]][i]; 53 que.push(next[now][i]); 54 } 55 } 56 } 57 } 58 void Query(char *str, vector<int > &ans) { 59 int len = strlen(str), now = root; 60 FOR(i, 0, len) { 61 now = next[now][str[i]]; 62 int tmp = now; 63 while (tmp != root) { 64 if (End[tmp]) ans.push_back(End[tmp]); 65 tmp = fail[tmp]; 66 } 67 } 68 } 69 70 } AC; 71 72 int N, M, res; 73 char str[10000 + 100]; 74 vector<int > ans[1000 + 100]; 75 76 int main() 77 { 78 AC.Init(); 79 scanf("%d", &N); 80 REP(i, 1, N) { 81 scanf("%s", str); 82 AC.Insert(str, i); 83 } 84 AC.Build(); 85 scanf("%d", &M); 86 FOR(i, 0, M) { 87 scanf("%s", str); 88 AC.Query(str, ans[i]); 89 } 90 res = 0; 91 FOR(i, 0, M) { 92 if ((int)ans[i].size()) { 93 printf("web %d:", i + 1); 94 sort(ans[i].begin(), ans[i].end()); 95 FOR(j, 0, (int)ans[i].size()) printf(" %d", ans[i][j]); 96 puts(""); 97 ++res; 98 } 99 } 100 printf("total: %d\n", res); 101 return 0; 102 }
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3065
思路:用一个数组来记录模式串在主串中出现的次数。
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 #include <queue> 6 #define FOR(i, a, b) for (int i = (a); i < (b); ++i) 7 #define REP(i, a, b) for (int i = (a); i <= (b); ++i) 8 using namespace std; 9 10 const int MAX_N = (50000 + 500); 11 12 int N, num[1000 + 100]; 13 char ss[1000 + 100][55]; 14 char str[2000000 + 200]; 15 16 struct Trie { 17 int next[MAX_N][128], End[MAX_N], fail[MAX_N]; 18 int root, L; 19 int NewNode() { 20 FOR(i, 0, 128) next[L][i] = -1; 21 End[L++] = -1; 22 return L - 1; 23 } 24 25 void Init() { 26 L = 0; 27 root = NewNode(); 28 } 29 30 void Insert(char *str, int index) { 31 int len = strlen(str), now = root; 32 FOR(i, 0, len) { 33 if (next[now][str[i]] == -1) next[now][str[i]] = NewNode(); 34 now = next[now][str[i]]; 35 } 36 End[now] = index; 37 } 38 39 void Build() { 40 queue<int > que; 41 fail[root] = root; 42 FOR(i, 0, 128) { 43 if (next[root][i] == -1) next[root][i] = root; 44 else { 45 fail[next[root][i]] = root; 46 que.push(next[root][i]); 47 } 48 } 49 while (!que.empty()) { 50 int now = que.front(); 51 que.pop(); 52 FOR(i, 0, 128) { 53 if (next[now][i] == -1) next[now][i] = next[fail[now]][i]; 54 else { 55 fail[next[now][i]] = next[fail[now]][i]; 56 que.push(next[now][i]); 57 } 58 } 59 } 60 } 61 62 void Query(char *str) { 63 memset(num, 0, sizeof(num)); 64 int len = strlen(str), now = root; 65 FOR(i, 0, len) { 66 now = next[now][str[i]]; 67 int tmp = now; 68 while (tmp != root) { 69 if (End[tmp] != -1) ++num[End[tmp]]; 70 tmp = fail[tmp]; 71 } 72 } 73 FOR(i, 0, N) { 74 if (num[i]) printf("%s: %d\n", ss[i], num[i]); 75 } 76 } 77 78 } AC; 79 80 81 int main() 82 { 83 while (~scanf("%d", &N)) { 84 AC.Init(); 85 scanf("%d", &N); 86 FOR(i, 0, N) { 87 scanf("%s", ss[i]); 88 AC.Insert(ss[i], i); 89 } 90 AC.Build(); 91 scanf("%s", str); 92 AC.Query(str); 93 } 94 return 0; 95 }
题目链接:http://poj.org/problem?id=2778
思路:需要用到的知识:有向图中点A到点B走K步的路径数等于有向图原始矩阵的K次幂。然后对于已经建好的Trie图,我们就可以建图了,如果某个节点A不是终止节点并且这个节点的next节点B也不是终止节点,那么就连边(表示从A点走1步到节点B的方法有1种)。建好图之后就是矩阵的快速幂了,然后在统计节点0(根节点)到其余节点走N步的方法数的总和。
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 #include <queue> 6 #define REP(i, a, b) for (int i = (a); i < (b); ++i) 7 #define FOR(i, a, b) for (int i = (a); i <= (b); ++i) 8 using namespace std; 9 10 const int MAX_N = (100 + 10); 11 const int MOD = (100000); 12 int M, N; 13 char str[22]; 14 15 struct Matrix { 16 long long mat[MAX_N][MAX_N]; 17 int n; 18 Matrix() {} 19 Matrix(int _n) 20 { 21 n = _n; 22 REP(i, 0, n) 23 REP(j, 0, n) mat[i][j] = 0; 24 } 25 Matrix operator *(const Matrix &b) const 26 { 27 Matrix c = Matrix(n); 28 REP(i, 0, n) { 29 REP(j, 0, n) { 30 REP(k, 0, n) { 31 c.mat[i][j] += mat[i][k] * b.mat[k][j]; 32 if (c.mat[i][j] >= MOD) c.mat[i][j] %= MOD; 33 } 34 } 35 } 36 return c; 37 } 38 39 }; 40 41 Matrix Pow(Matrix mat, int n) 42 { 43 Matrix ONE = Matrix(mat.n); 44 REP(i, 0, mat.n) ONE.mat[i][i] = 1; 45 Matrix tmp = mat; 46 while (n) { 47 if (n & 1) ONE = ONE * tmp; 48 n >>= 1; 49 tmp = tmp * tmp; 50 } 51 return ONE; 52 } 53 54 struct Trie { 55 int next[MAX_N][4], End[MAX_N], fail[MAX_N]; 56 int L, root; 57 int NewNode() 58 { 59 REP(i, 0, 4) next[L][i] = -1; 60 End[L++] = 0; 61 return L - 1; 62 } 63 64 void Init() 65 { 66 L = 0; 67 root = NewNode(); 68 } 69 70 int getID(char ch) 71 { 72 if (ch == 'A') return 0; 73 if (ch == 'C') return 1; 74 if (ch == 'G') return 2; 75 if (ch == 'T') return 3; 76 } 77 78 void Insert(char *str) 79 { 80 int len = strlen(str), now = root; 81 REP(i, 0, len) { 82 int id = getID(str[i]); 83 if (next[now][id] == -1) next[now][id] = NewNode(); 84 now = next[now][id]; 85 } 86 End[now] = 1; 87 } 88 89 void Build() 90 { 91 queue<int > que; 92 fail[root] = root; 93 REP(i ,0, 4) { 94 if (next[root][i] == -1) next[root][i] = root; 95 else { 96 fail[next[root][i]] = root; 97 que.push(next[root][i]); 98 } 99 } 100 while (!que.empty()) { 101 int now = que.front(); 102 que.pop(); 103 if (End[fail[now]]) End[now] = 1; 104 REP(i, 0, 4) { 105 if (next[now][i] == -1) next[now][i] = next[fail[now]][i]; 106 else { 107 fail[next[now][i]] = next[fail[now]][i]; 108 que.push(next[now][i]); 109 } 110 } 111 } 112 } 113 114 Matrix getMatrix() 115 { 116 Matrix res = Matrix(L); 117 REP(i, 0, L) 118 REP(j, 0, 4) if (!End[next[i][j]]) ++res.mat[i][next[i][j]]; 119 return res; 120 } 121 122 } AC; 123 124 125 int main() 126 { 127 while (~scanf("%d %d", &M, &N)) { 128 AC.Init(); 129 FOR(i, 1, M) scanf("%s", str), AC.Insert(str); 130 AC.Build(); 131 Matrix tmp = AC.getMatrix(); 132 tmp = Pow(tmp, N); 133 long long ans = 0; 134 REP(i, 0, tmp.n) { 135 ans += tmp.mat[0][i]; 136 if (ans >= MOD) ans %= MOD; 137 } 138 printf("%lld\n", ans); 139 } 140 return 0; 141 }
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=224
思路:出现过给定单词的单词数 = 所有的单词数 - 没有出现过给定单词的单词数, 而所有的单词数 = 26^1 + 26^2 + ... + 26^L,没有出现过给定单词的单词数 = A + A^2 + ... + A^n,其中A是根据能走的字符之间的路径数建立的邻接矩阵。
那么如何求出A + A^2 + A^3 + ... + A^L?
可以这样做,依据等比矩阵的特点有:
于是可以很快计算出A^1+ A ^2 + ... + A ^L 以及 26^1 + 26^2 + ... + 26 ^ L的值。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#define REP(i, a, b) for (int i = (a); i < (b); ++i)
#define FOR(i, a, b) for (int i = (a); i <= (b); ++i)
using namespace std;
const int MAX_N = (33);
int N, L;
typedef unsigned long long ULL;
struct Matrix {
ULL mat[MAX_N][MAX_N];
int n;
Matrix(){}
Matrix(int _n) : n(_n) {
REP(i, 0, n)
REP(j, 0, n) mat[i][j] = 0;
}
};
Matrix Mul(const Matrix &a, const Matrix &b)
{
Matrix c = Matrix(a.n);
REP(i, 0, a.n) {
REP(j, 0, a.n)
REP(k, 0, a.n) c.mat[i][j] += a.mat[i][k] * b.mat[k][j];
}
return c;
}
Matrix Pow(Matrix p,int n)
{
Matrix ONE = Matrix(p.n);
REP(i, 0, ONE.n) ONE.mat[i][i] = 1;
while (n) {
if (n & 1) ONE = Mul(ONE, p);
n >>= 1;
p = Mul(p, p);
}
return ONE;
}
struct Trie {
int next[MAX_N][MAX_N], end[MAX_N], fail[MAX_N];
int root, L;
void Init() {
L = 0;
root = NewNode();
}
int NewNode() {
REP(i, 0, 26) next[L][i] = -1;
end[L++] = 0;
return (L - 1);
}
void Insert(char *str) {
int len = strlen(str), now = root;
REP(i, 0, len) {
int index = str[i] - 'a';
if (next[now][index] == -1) next[now][index] = NewNode();
now = next[now][index];
}
end[now] = 1;
}
void Build() {
queue<int > que;
fail[root] = root;
REP(i, 0, 26) {
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();
if (end[fail[now]]) end[now] = 1;
REP(i, 0, 26) {
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]);
}
}
}
}
Matrix getMatrix() {
Matrix tmp = Matrix(L + 1);
REP(i, 0, L) {
REP(j, 0, 26) if (!end[next[i][j]]) ++tmp.mat[i][next[i][j]];
}
REP(i, 0, L + 1) tmp.mat[i][L] = 1;
return tmp;
}
} AC;
int main()
{
while (cin >> N >> L) {
AC.Init();
FOR(i, 1, N) {
char str[MAX_N]; cin >> str;
AC.Insert(str);
}
AC.Build();
Matrix a = AC.getMatrix();
a = Pow(a, L);
ULL ans(0);
REP(i, 0, a.n) ans += a.mat[0][i];
ans -= 1;
a = Matrix(2);
a.mat[0][0] = 26, a.mat[0][1] = a.mat[1][1] = 1;
a = Pow(a, L);
ULL sum = a.mat[0][0] + a.mat[0][1];
sum -= 1;
cout << (sum - ans) << endl;
}
return 0;
}
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2825
思路:建立AC自动机,每个节点都代表一种状态,建立fail指针的时候顺便更新以该节点结尾的字符串代表的状态。
dp[i][j][k]代表最终密码长度为i,以AC自动机的位置j所在的字符结尾的,并且状态为k的密码的个数。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#define REP(i, a, b) for (int i = (a); i < (b); ++i)
#define FOR(i, a, b) for (int i = (a); i <= (b); ++i)
using namespace std;
const int MAX_N = (10 * 10 + 10);
const int MOD = (20090717);
int N, M, K;
int num[1 << 11], dp[27][MAX_N][1 << 11]; //dp[i][j][k]表示长度为i,在AC自动机上的节点为L,并且状态为k的符合条件的个数
struct Trie {
int next[MAX_N][26], end[MAX_N], fail[MAX_N];
int root, L;
void Init() {
L = 0;
root = NewNode();
}
int NewNode() {
REP(i, 0, 26) next[L][i] = -1;
end[L++] = 0;
return (L - 1);
}
void Insert(char *str, int ID) {
int len = strlen(str), now = root;
REP(i, 0, len) {
int index = str[i] - 'a';
if (next[now][index] == -1) next[now][index] = NewNode();
now = next[now][index];
}
end[now] |= (1 << ID);
}
void Build() {
queue<int > que;
fail[root] = root;
REP(i, 0, 26) {
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]];
REP(i, 0, 26) {
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]);
}
}
}
}
int getDp() {
FOR(i, 0, N) {
REP(j, 0, L)
REP(s, 0, (1 << M)) dp[i][j][s] = 0;
}
dp[0][0][0] = 1;
REP(i, 0, N) {
REP(j, 0, L) {
REP(s, 0, (1 << M)) if (dp[i][j][s]) {
REP(k, 0, 26) {
dp[i + 1][next[j][k]][s | end[next[j][k]]] += dp[i][j][s];
if (dp[i + 1][next[j][k]][s | end[next[j][k]]] >= MOD) dp[i + 1][next[j][k]][s | end[next[j][k]]] %= MOD;
}
}
}
}
int ans = 0;
REP(s, 0, (1 << M)) if (num[s] >= K) {
REP(i, 0, L) {
ans += dp[N][i][s];
if (ans >= MOD) ans %= MOD;
}
}
return ans;
}
} AC;
int main()
{
memset(num, 0, sizeof(num));
REP(s, 0, (1 << 12)) {
REP(i, 0, 12) if (s & (1 << i)) ++num[s];
}
while (cin >> N >> M >> K) {
if (N == 0 && M == 0 && K == 0) break;
AC.Init();
REP(i, 0, M) {
char str[11]; cin >> str;
AC.Insert(str, i);
}
AC.Build();
printf("%d\n",AC.getDp());
}
return 0;
}
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2296
思路:建立Trie, end节点代表的该串的价值,dp[i][j]表示长度为i,在自动机上的位置为j的子串对应的价值。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <string>
#include <queue>
#define REP(i, a, b) for (int i = (a); i < (b); ++i)
#define FOR(i, a, b) for (int i = (a); i <= (b); ++i)
using namespace std;
const int MAX_N = (1100 + 100);
int N, M, val[110];
int dp[55][MAX_N]; //dp[i][j] : 长度为i,以AC自动机上的位置j的字符为结尾的串的最大价值
string str[55][MAX_N];
bool cmp(const string &str1, const string &str2)
{
if ((int)str1.size() != (int)str2.size()) return (int)str1.size() < (int)str2.size();
return str1 < str2;
}
struct Trie {
int next[MAX_N][26], end[MAX_N], fail[MAX_N];
int root, L;
void Init() {
L = 0;
root = NewNode();
}
int NewNode() {
REP(i, 0, 26) next[L][i] = -1;
end[L++] = -1;
return (L - 1);
}
void Insert(char *str, int ID) {
int len = strlen(str), now = root;
REP(i, 0, len) {
int index = str[i] - 'a';
if (next[now][index] == -1) next[now][index] = NewNode();
now = next[now][index];
}
end[now] = ID;
}
void Build() {
queue<int > que;
fail[root] = root;
REP(i, 0, 26) {
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();
REP(i, 0, 26) {
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]);
}
}
}
}
void getAns() {
string ans("");
int maxval = 0, vv = 0;
memset(dp, -1, sizeof(dp));
dp[0][0] = 0;
str[0][0] = "";
REP(i, 0, N) {
REP(j, 0, L) if (dp[i][j] >= 0) {
REP(k, 0, 26) {
string tmp = str[i][j];
tmp += (char)('a' + k);
vv = dp[i][j];
if (end[next[j][k]] != -1) vv += val[end[next[j][k]]];
if (dp[i + 1][next[j][k]] < vv || (dp[i + 1][next[j][k]] == vv && cmp(tmp, str[i + 1][next[j][k]]))) {
dp[i + 1][next[j][k]] = vv;
str[i + 1][next[j][k]] = tmp;
if (vv > maxval || (vv == maxval && cmp(tmp, ans))) {
ans = tmp;
maxval = vv;
}
}
}
}
}
cout << ans << endl;
}
} AC;
int main()
{
int Cas;
cin >> Cas;
while (Cas--) {
cin >> N >> M;
AC.Init();
REP(i, 0, M) {
char str[20]; cin >> str;
AC.Insert(str, i);
}
REP(i, 0, M) cin >> val[i];
AC.Build();
AC.getAns();
}
return 0;
}
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2457
思路:dp[i][j]表示长度为i,在AC自动机上的位置为j满足条件的所需替换的字符的个数的最小值。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <string>
#include <queue>
#define REP(i, a, b) for (int i = (a); i < (b); ++i)
#define FOR(i, a, b) for (int i = (a); i <= (b); ++i)
using namespace std;
const int MAX_N = (1000 + 10);
template < typename T > inline T getMIN(const T &a, const T &b)
{
return a < b ? a : b;
}
int N, dp[MAX_N][MAX_N]; //len, pos;
char str[MAX_N];
struct Trie {
int next[MAX_N][4], end[MAX_N], fail[MAX_N];
int L, root;
void Init() {
L = 0;
root = NewNode();
}
int NewNode() {
REP(i, 0, 4) next[L][i] = -1;
end[L++] = 0;
return (L - 1);
}
int getchange(char ch) {
if (ch == 'A') return 0;
if (ch == 'C') return 1;
if (ch == 'G') return 2;
if (ch == 'T') return 3;
return -1;
}
void Insert(char *str) {
int len = strlen(str), now = root;
REP(i, 0, len) {
int id = getchange(str[i]);
if (next[now][id] == -1) next[now][id] = NewNode();
now = next[now][id];
}
end[now] = 1;
}
void Build() {
queue<int > que;
fail[root] = root;
REP(i, 0, 4) {
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();
if (end[fail[now]]) end[now] = 1;
REP(i, 0, 4) {
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]);
}
}
}
}
int getDp(char *str) {
memset(dp, 0x3f, sizeof(dp));
int len = strlen(str);
dp[0][0] = 0;
REP(i, 0, len) {
REP(j, 0, L) if (!end[j] && dp[i][j] < 0x3f3f3f3f){
REP(k, 0, 4) if (!end[next[j][k]]) {
int val = (k != getchange(str[i]));
dp[i + 1][next[j][k]] = getMIN(dp[i + 1][next[j][k]], dp[i][j] + val);
}
}
}
int ans = 0x3f3f3f3f;
REP(i, 0, L) ans = getMIN(ans, dp[len][i]);
if (ans == 0x3f3f3f3f) return -1;
return ans;
}
} AC;
int main()
{
int t = 1;
while (cin >> N && N) {
AC.Init();
REP(i, 0, N) {
char ss[22]; cin >> ss;
AC.Insert(ss);
}
cin >> str;
AC.Build();
printf("Case %d: %d\n", t++, AC.getDp(str));
}
return 0;
}