1.应用:用于多个模式串与一个文本串的匹配问题
2.原理:将模式串建立trie树,运用kmp的思想匹配文本串
3.基础操作:
1.insert(同trie树)
void insert(char *s,int l,int x){
int p=0;
for(int i=0;i<l;i++){
int ty=s[i]-'a';
if(!dic[p][ty]) dic[p][ty]=++num;
p=dic[p][ty];
}
id[p].push_back(x);
}
2.get_fail:是AC自动机的核心思想,原理和kmp中的前缀数组类似,就是若模式串与文本串失配,不用从头匹配,而是从失配位置的fail数组匹配
void get_fail(){
queue<int>q;
for(int i=0;i<26;i++){//先预处理出来0号节点的
if(dic[0][i]){
q.push(dic[0][i]);
fail[dic[0][i]]=0;
}
}
while(!q.empty()){
int p=q.front();
q.pop();
for(int i=0;i<26;i++){
if(dic[p][i]){
fail[dic[p][i]]=dic[fail[p]][i];//若有这个节点,它的fail就是父亲的fail沿代表字母i的边走到的节点
q.push(dic[p][i]);
}else{
dic[p][i]=dic[fail[p]][i];
}
}
}
}
3.find
int find(char *s,int l){
int p=0,ans=0;
for(int i=0;i<l;i++){
int ty=s[i]-'a';
p=dic[p][ty];
int h=p;
while(!ok[h]&&h){
ok[h]=true;//若h访问过了,需要打上标记保证不能重复计算
ans+=gs[h];
h=fail[h];
}
}
return ans;
}
时间复杂度:O( s1+s2)
若要统计出现次数,需要将模式串与文本串的每一位都匹配,极限数据(sssssss……)下会退化成做n次kmp
优化:比如本次需要更新h,需要更新fail[h],fail[fail[h]]......,若再次更新fail[h],则还需更新fail[fail[h]].....,时间复杂度在此浪费。
可以将每次更新打上标记,最后dfs一边更新完毕
get_fail:
void get_fail(){
queue<int>q;
for(int i=0;i<26;i++){
if(dic[0][i]){
fail[dic[0][i]]=0;
q.push(dic[0][i]);
}
}
while(!q.empty()){
int p=q.front();
q.pop();
for(int i=0;i<26;i++){
if(dic[p][i]){
fail[dic[p][i]]=dic[fail[p]][i];
in[fail[dic[p][i]]]++;//每个dic[p][i]都会连向他的fail一条边,所以入度++
q.push(dic[p][i]);
}else{
dic[p][i]=dic[fail[p]][i];
}
}
}
}
find:
void find(char *s,int l){
int p=0;
for(int i=0;i<l;i++){
int ty=s[i]-'a';
p=dic[p][ty];
ans[p]++;//对于每个字符,打上标记
}
}
得到答案(应用了拓扑序):
void topu(){
queue<int>q;
for(int i=1;i<=num;i++){
if(!in[i]) q.push(i);
}
while(!q.empty()){
int p=q.front();
q.pop();
for(int i=0;i<id[p].size();i++) cs[id[p][i]]=ans[p];
int to=fail[p];
in[to]--;
ans[to]+=ans[p];//每个父亲节点加上儿子节点的答案
if(!in[to]) q.push(to);
}
}
AC自动机上dp:
思路:在AC自动机上每个字符串的末尾+1,然后对于AC自动机做DP,dp[i][j]表示位于节点i,遍历了j个节点的ans最大值
#include<bits/stdc++.h>
using namespace std;
struct ac{
int dic[1010][3],fs[1010],num,fail[1010],l,dp[1010][1010];
void insert(char *s,int l){
int p=0;
for(int i=0;i<l;i++){
int ty=s[i]-'A';
if(!dic[p][ty]) dic[p][ty]=++num;
p=dic[p][ty];
}
fs[p]++;
}
void get_fail(){
queue<int>q;
for(int i=0;i<3;i++){
if(dic[0][i]){
fail[dic[0][i]]=0;
q.push(dic[0][i]);
}
}
while(!q.empty()){
int p=q.front();
q.pop();
for(int i=0;i<3;i++){
if(dic[p][i]){
q.push(dic[p][i]);
if(fs[dic[fail[p]][i]]) fs[dic[p][i]]+=fs[dic[fail[p]][i]];//如果这个节点的fail上有标记,这个节点也需要打上标记
fail[dic[p][i]]=dic[fail[p]][i];
}else{
dic[p][i]=dic[fail[p]][i];
}
}
}
}
int dfs(int p,int x){//记忆化搜索
if(x>l) return 0;
if(dp[p][x]) return dp[p][x];
int ans=0;
for(int i=0;i<3;i++){
ans=max(ans,dfs(dic[p][i],x+1)+(x==l?0:fs[dic[p][i]]));//如果长度==l,就不能加了
}
return dp[p][x]=ans;
}
};
ac ty;
int a,b,c,d;
int main(){
scanf("%d%d",&a,&ty.l);
for(int i=1;i<=a;i++){
char s[20];
scanf("%s",s);
ty.insert(s,strlen(s));
}
ty.get_fail();
printf("%d",ty.dfs(0,0));
return 0;
}