第一步:构建Trie树
和普通的trie树构建方法一样
如:依次插入字符串apple、aply、cold、cool、cook
struct node {
int fail;//失配指针
int son[26];//儿子节点
int end_num;//结尾标记
}trie[N];
int pos;//tire的指针
void build_trie(string s) {//建立字典树
int now = 0;
for (int i = 0; i < s.length(); i++) {
int t = s[i] - 'a';
if (!trie[now].son[t])trie[now].son[t] = ++pos;
now = trie[now].son[t];
}
trie[now].end_num++;
}
第二步:构建fail
fail指针就是当前位置失配以后能够跳转继续进行匹配的字符位置,达到匹配过程中不需要回溯的效果。
构建过程使用了BFS:
假设当前节点为v, 是其父节点u的儿子
得到父节点的fail指针所指节点ptr_fail
遍历ptr_fail的子节点中是否有和v节点值相同的节点,若存在则v的fail节点指向该节点,不存在则继续遍历ptr_fail的子节点,直到找到与相同值的节点或达到root节点0
void get_fail() {//构建fail指针
queue<int> q;
for (int i = 0; i < 26; i++) {//首先预处理第一层的节点
int t = trie[0].son[i];
if (t) {
trie[t].fail = 0;
q.push(t);
}
}
while (!q.empty()) {
int u = q.front(); q.pop();//取出节点u
for (int i = 0; i < 26; i++) {//枚举节点u的所有儿子
int v = trie[u].son[i];//节点u的某个儿子v(实质上我们是构造v的fail指针)
int ptr_fail = trie[u].fail;//节点u的fail指针所指节点(v节点父节点u的fail指针指向节点)
if (v) {//如果子节点v存在的话
//子节点v的fail指针指向 当前节点的fail指针所指向的节点的相同子节点(默认为0即跟根节点)
trie[v].fail = trie[ptr_fail].son[i];
q.push(v);
}
//否则当前这个儿子v指向 当前节点fail指针所指向的节点的相同子节点(默认为0即跟根节点)
else trie[u].son[i] = trie[ptr_fail].son[i];
}
}
}
第三步:模式匹配
匹配的核心是从目标串从头逐个开始,在ac自动机中进行匹配,匹配上的则计数,若未匹配上则跳转失配位置进行尝试匹配,直到全部匹配完成
int query(string s) {
int ans = 0;
int now = 0;
for (int i = 0; i < s.length(); i++) {//从目标串从头逐个开始,在ac自动机中进行匹配
int t = s[i] - 'a';
now = trie[now].son[t];
//匹配上的则计数,若未匹配上则跳转失配位置进行尝试匹配,直到全部匹配完成
for (int j = now; j && trie[j].end_num != -1; j = trie[j].fail) {
ans += trie[j].end_num;
trie[j].end_num = -1;//标记已统计过
}
}
return ans;
}
#include<bits/stdc++.h>
#include <unordered_map>
using namespace std;
template<class...Args>
void debug(Args... args) {//Parameter pack
auto tmp = { (cout << args << ' ', 0)... };
cout << "\n";
}
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll, ll>pll;
typedef pair<int, int>pii;
const ll N = 1e6 + 5;
const ll MOD = 998244353;
const ll INF = 0x7fffffff;
struct node {
int fail;//失配指针
int son[26];//儿子节点
int end_num;//结尾标记
}trie[N];
int pos;//tire的指针
void build_trie(string s) {//建立字典树
int now = 0;
for (int i = 0; i < s.length(); i++) {
int t = s[i] - 'a';
if (!trie[now].son[t])trie[now].son[t] = ++pos;
now = trie[now].son[t];
}
trie[now].end_num++;
}
void get_fail() {//构建fail指针
queue<int> q;
for (int i = 0; i < 26; i++) {//首先预处理第一层的节点
int t = trie[0].son[i];
if (t) {
trie[t].fail = 0;
q.push(t);
}
}
while (!q.empty()) {
int u = q.front(); q.pop();//取出当前节点u
for (int i = 0; i < 26; i++) {//枚举节点u的所有儿子
int v = trie[u].son[i];//节点u的某个儿子v(实质上我们是构造v的fail指针)
int ptr_fail = trie[u].fail;//节点u的fail指针所指节点(v节点父节点u的fail指针指向节点)
if (v) {//如果子节点v存在的话
//子节点v的fail指针指向 当前节点的fail指针所指向的节点的相同子节点(默认为0即跟根节点)
trie[v].fail = trie[ptr_fail].son[i];
q.push(v);
}
//否则当前这个儿子v指向 当前节点fail指针所指向的节点的相同子节点(默认为0即跟根节点)
else trie[u].son[i] = trie[ptr_fail].son[i];
}
}
}
int query(string s) {
int ans = 0;
int now = 0;
for (int i = 0; i < s.length(); i++) {//从目标串从头逐个开始,在ac自动机中进行匹配
int t = s[i] - 'a';
now = trie[now].son[t];
//匹配上的则计数,若未匹配上则跳转失配位置进行尝试匹配,直到全部匹配完成
for (int j = now; j && trie[j].end_num != -1; j = trie[j].fail) {
ans += trie[j].end_num;
trie[j].end_num = -1;//标记已统计过
}
}
return ans;
}
int main() {
ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int n;
cin >> n;
string temp;
for (int i = 0; i < n; i++) {
cin >> temp;
build_trie(temp);
}
get_fail();
string text;
cin >> text;
cout << query(text) << "\n";
return 0;
}