前提 :
1. KMP 单模式匹配原理
2. 字典树结构
失配函数获取:
1. 找当前点s的失配节点下标为c, 先找到找父节点f的失配节点fail[f], 如果fail[f]存在下标为c子节点, 那么fail[s] = ch[fail[f]][c]
不存在沿着失配指针回推fail[….fail[f]]直至根节点
2. 由于是树形, 所以一般采取bfs递推fail值, 而不是KMP的线性递归
3. 实现过程 :
1) 根节点的子结点fail值全为0, 入队
2) 第三层开始, bfs入队, 回推fail(找父节点fail, KMP思想)
ps:还可以记下以该节点为尾的字符串的最大后缀串(模板式串中存在的)
比如如果有3个模式串AZAZAB, AZA, ZA那么 第一个串的最后一个‘A’的last值应该是AZA的最后一个‘A’的节点下标, 这也说明多模式串中同一个节点可能对应多个串的串尾
code:
struct AC_Auto{
int ch[10010][26], last[10010], val[10010], f[10010];
string tmp[10010];/**存节点字符串*/
int sz;
int idx(char s){return s - 'A';}
AC_Auto(){
memset(ch, 0, sizeof ch);
sz = 1;
memset(val, 0, sizeof val);
memset(last, 0, sizeof last);/**初始化*/
memset(f, 0, sizeof f);
}
void insert(string w){/**一般字典树建树*/
int u = 0;
for(int i = 0; w[i]; ++i){
int c = idx(w[i]);
if(!ch[u][c]){
ch[u][c] = sz++;
}
u = ch[u][c];
}
val[u] = 1;
tmp[u] = w;/**标记单词, 记下单词*/
}
void getFail(){
queue<int> q;
q.push(0);/**头结点*/
while(!q.empty()){
int t = q.front();
q.pop();
for(int c = 0; c < 26; ++c){
if(ch[t][c]){/**子节点*/
int u = ch[t][c], v = f[t];
q.push(u);
while(v && !ch[v][c]) v = f[v];/**回推获取失配/
/**这里如果退回到根节点, 根节点下标为c节点也可能存在*/
f[u] = t ? ch[v][c] : 0;/**第二层直接fail = 0*/
last[u] = val[f[u]] ? f[u] : last[f[u]];
/**获取last*/
cout << "u : " << u << " " << last[u] << endl;
}
}
}
}
int query(){
int u = 0;
for(int i = 0; s[i]; ++i){
int c = idx(s[i]);
while(u && !ch[u][c]) u = f[u];
u = ch[u][c];/**查询*/
if(val[u]) print(u);/**找到一个单词*/
else if(last[u]) print(last[u]);/**找到模式串中该节点表示字符串最大后缀的单词*/
}
return 1;
}
void print(int x){/**递归打印*/
if(x){
cout << tmp[x] << endl;
print(last[x]);
}
}
};
POJ 3461 AC自动机解法
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int N = 1000010;
char s[N], w[10010];
struct AC_Auto{
int ch[10010][26], last[10010], val[10010], f[10010];
int sz;
int idx(char s){return s - 'A';}
AC_Auto(){
memset(ch, 0, sizeof ch);
sz = 1;
memset(val, 0, sizeof val);
memset(last, 0, sizeof last);
memset(f, 0, sizeof f);
}
void insert(){
int u = 0;
for(int i = 0; w[i]; ++i){
int c = idx(w[i]);
if(!ch[u][c]){
ch[u][c] = sz++;
}
u = ch[u][c];
}
val[u] = 1;
}
void getFail(){
queue<int> q;
q.push(0);
while(!q.empty()){
int t = q.front();
q.pop();
for(int c = 0; c < 26; ++c){
if(ch[t][c]){
int u = ch[t][c], v = f[t];
q.push(u);
while(v && !ch[v][c]) v = f[v];
f[u] = t ? ch[v][c] : 0;
last[u] = val[f[u]] ? f[u] : last[f[u]];
}
}
}
}
int query(){
int u = 0, ans = 0;
for(int i = 0; s[i]; ++i){
int c = idx(s[i]);
while(u && !ch[u][c]) u = f[u];
u = ch[u][c];
if(val[u]) ++ans, u = f[u];/**找到之后回退失配节点, 同裸KMP*/
}
return ans;
}
};
int main(){
int t;
cin >> t;
while(t--){
cin >> w >> s;
AC_Auto ac;
ac.insert();
ac.getFail();
cout << ac.query() << endl;
}
return 0;
}