定义 1
\(Pattern\):模式串
\(Text\):文本串
\(next\):失配函数, \(next[i]=j\) 表示 \(Pattern\) 的最大自匹配的真前缀(\(|pre| \lt len\))的下标(这里是为了方便代码实现),即模式串的子串 \(P[0,j] = P[i-j,i]\),并且 \(next[0] = -1\)
构造过程
构造过程就是初始化 \(next\) 的过程
假设存在 \(i,j\) 指针,\(i\) 为当前要处理的指针,而 \(j\) 对应于上一次 \(i-1\) 时匹配的失配指针
就是说有 \(nxt[i-1]=j\)
说明最大的自匹配为 \(P[0,j]=P[t,i-1]\),\(t\) 多少不必在意,只需知道两子串长度相等即可
最理想的情况下,\(P[0,j+1] = P[t,i]\),则直接完成当前初始化
否则需要 \(j = next[j]\) 进行迭代,贪心找到尽可能长的自匹配,使得满足 \(P[0,next[next...[j]]+1] = P[t',i]\),\(j\) 增加 \(1\)
(因为 \(next\) 都是描述真前缀和真后缀的自匹配关系,所以只需判断当前的 \(P[i]\) 和 \(P[next...[j]]\) 是否相等,这是减少复杂度的关键)
\(i\) 永远单调增大,\(next[j]\) 每次最多单调增加 1
极端情况就是减少到 \(next[j]=-1\) 仍未满足匹配关系
匹配过程
其思路是 \(T\) 匹配的后缀可能性由 \(P\) 来提供
以 \(T\) 的后缀和 \(P\) 的前缀作为比较,此时 \(nxt\) 用于对付 \(T\) 的后缀失配的情况
整体流程和构造过程相似,但需要多处理一个越界的情况
完成版
#include <bits/stdc++.h>
struct Matcher {
std::string pattern;
std::vector<int> next;
Matcher(std::string p):
pattern(std::move(p)), next(pattern.length())
{ initNext(); }
void initNext() {
next[0] = -1;
for(int i = 1, j = -1; i < pattern.length(); ++i) {
for(; j > -1 && pattern[i] != pattern[j+1]; j = next[j]);
if(pattern[i] == pattern[j+1]) ++j;
next[i] = j;
}
}
int execute(const std::string &text) {
int count = 0;
for(int i = 0, j = -1; i < text.length(); ++i) {
// 需要处理**越界**或失配
for(; j > -1 && (j+1 >= text.length() || text[i] != pattern[j+1]); j = next[j]);
if(text[i] == pattern[j+1]) ++j;
// matchAt[i] = j;
if(j+1 == pattern.length()) ++count;
}
return count;
}
};
int main() {
for(std::string p, t; std::cin >> p >> t;) {
std::cout << Matcher(p).execute(t) << std::endl;
}
return 0;
}
感觉 EXKMP
用处并不大,教程不写了
至于 AC自动机
,其实就是定义 \(fail[u]\) 为 \(root \to u\) 的最长相同后缀的转移节点
其余还有构造时的路径压缩,以及查询时可能用到的拓扑排序优化
然后没了
#include <bits/stdc++.h>
namespace utils {
template <typename Container, typename ...Args>
inline void append(Container &c, Args &&...args) {
c.emplace_back(std::forward<Args>(args)...);
}
inline void gkd() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
}
template <unsigned int init = 19260817>
inline int random() {
static auto seed = init;
seed = seed * 998244353 + 12345;
return static_cast<int>(seed / 1024);
}
void debug() { std::cout << std::endl; }
template <typename Arg, typename ...Args>
void debug(Arg arg, Args ...args) {
std::cout << arg << ' ';
debug(args...);
}
}
struct Trie {
std::vector<std::array<int, 26>> dict;
std::vector<bool> flag;
std::vector<int> end;
int root;
Trie(): root(node()) { }
int node() {
auto cur = dict.size();
utils::append(dict);
utils::append(flag, false);
utils::append(end, 0);
return cur;
}
void add(const std::string &str) {
auto cur = root;
for(auto ch : str) {
auto c = ch - 'a';
if(!dict[cur][c]) {
auto t = node();
dict[cur][c] = t;
}
cur = dict[cur][c];
}
flag[cur] = true;
++end[cur];
}
std::array<int, 26>& operator[](size_t pos) {
return dict[pos];
}
};
struct AcAutomaton {
Trie trie;
std::vector<int> fail;
int root;
AcAutomaton(const std::vector<std::string> &strs): root(0) {
for(auto &str : strs) trie.add(str);
fail.resize(trie.dict.size());
std::queue<int> que;
fail[root] = root;
for(int i = 0, j = 0; i < 26; ++i) {
if(j = trie[root][i]) {
fail[j] = root;
que.push(j);
}
}
while(!que.empty()) {
auto u = que.front();
que.pop();
for(int i = 0; i < 26; ++i) {
auto v = trie[u][i];
auto f = fail[u];
if(v) {
fail[v] = trie[f][i];
que.push(v);
} else {
trie[u][i] = trie[f][i];
}
}
}
}
long long query(const std::string &str) {
auto cur = root;
long long res = 0;
for(auto ch : str) {
auto c = ch - 'a';
cur = trie[cur][c];
for(auto u = cur; u && ~trie.end[u]; u = fail[u]) {
res += trie.end[u];
trie.end[u] = -1;
}
}
return res;
}
};
int main() {
utils::gkd();
int testcase;
std::cin >> testcase;
while(testcase--) {
int n;
std::cin >> n;
std::vector<std::string> vec(n);
for(auto &i : vec) std::cin >> i;
AcAutomaton ac(vec);
std::string s;
std::cin >> s;
std::cout << ac.query(s) << std::endl;
}
return 0;
}