#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 5;
const char Ch = 'a';
struct node {
int next[30];
int idx, fail, cnt;//idx表示输入的第几个字符串的终止位置,fail是失配指针,cnt存该字符串出现的次数
void clear() {
memset(next, 0, sizeof(next));
idx = 0, fail = -1, cnt = 0;
}
} t[maxn];
int tot;//指针.
void init() {
cout<<tot;
for(int i = 0; i <= tot; i ++) t[i].clear();
tot = 0;
}
void insert(string a) {
int len = a.length(), pos = 0;//此时的tot指的是节点总个数.
for(int i = 0; i < len; i ++) {
int tmp = a[i] - Ch;
if(!t[pos].next[tmp]) t[pos].next[tmp] = ++tot;//判断该长度的字符是否存在.最终导致每个不存在的字符都会新开一个结构体.
pos = t[pos].next[tmp];//若存在长度按记录值,若不存在长度++并记录.
} //这里的pos表示字符串终止的位置,通常在这里进行操作
t[pos].cnt ++;//字符串出现次数+1
}
void getFail() {
t[0].fail = 0;
queue<int> q;
for(int i = 0; i < 26; i ++) { //将树的第一层的所有节点的fail指针初始化为0即指向root节点.
int pos = t[0].next[i];
if(pos) {
t[pos].fail = 0;
q.push(pos);//将每个节点的指针tot入队.
}
}
while(q.size()) {
int pos = q.front();
q.pop();//此时的pos为指向下一层的指针.
for(int i = 0; i < 26; i ++) {
int son = t[pos].next[i], fail_pos = t[pos].fail;//son为pos的子节点的指针
if(son) { //子节点存在,将子节点的指fail针指更新为父亲节点的fail指针
t[son].fail = t[fail_pos].next[i];//如果父节点的失配指针指向的节点fail_pos 他也有值是i的子节点,那么son的失配指针就可以指向那个子节点了.
q.push(son);//将子节点入队
} else {
t[pos].next[i] = t[fail_pos].next[i];//同个前缀不同字符指向相同节点,就是一路往回退,最后都会退回root节点的
}
}
}
}
int query(string s) {
int len = s.length(), pos = 0, ans = 0, j;
for(int i = 0; i < len; i ++) {
pos = t[pos].next[s[i] - Ch];
j = pos;
while(j && t[j].cnt != -1) { //加过下次就不加了,标记一下
ans += t[j].cnt;
t[j].cnt = -1;
j = t[j].fail;//进入fail指针指向的节点
}
}
return ans;
}
void solve() {
// init();
int n;
cin >> n;
for(int i = 1; i <= n; i ++) { //建树 .
string a;
cin >> a;
insert(a);
}
getFail();//建立fail指针.
string s;
cin >> s;
cout << query(s) << endl;//查询,并输出
}
int main() {
solve();
return 0;
}
算法介绍可参考:AC自动机(算法介绍)_kilig_CSM的博客-CSDN博客https://blog.csdn.net/kilig_CSM/article/details/119341634