ac自動機是一種基於trie樹的算法,其本質和kmp上的處理很相似。
trie樹結構:https://blog.csdn.net/qq_38890926/article/details/81158021
kmp轉移思路:https://blog.csdn.net/qq_38890926/article/details/81158132
ac自動機組要由三個部分組成:trie樹的建立 fail指針的匹配 對ac自動機的詢問
每次建立自動機會有一次初始化
ac自動機類
struct node //node結構體
{
int exist;
node* next[26],fail;
node() //初始化
{
exist=0;
fail=NULL;
memset(next,0,sizeof(next));
}
};
struct AC
{
node *root;
const int SIZE=26;
AC(){root=newnode();}
void init(){del(root);root=newnode();} //時間換空間
//void init(){root=newnode();} //空間換時間
node* newnode(){return new node;}
//int idx(char c){return c;} //size=128,可見字符在128以內
inline int idx(char c){return c-'a';} //size=26
void insert(char *s,int num) //插入字符串建樹
{
node *u=root;
int len=strlen(s);
for(int i=0;i<len;i++)
{
int c=idx(s[i]);
if(u->next[c]==NULL)u->next[c]=new node();
u=u->next[c];
}
u->exist=num;
/*其他操作,在每次插入字符串的結尾node上進行操作*/
}
queue<node*> q; //fail指針的構建,採用bfs
void build()
{
while(!q.empty())q.pop();q.push(root);
while(!q.empty())
{
node* u=q.front();q.pop();//u也有u指向fail位置的性質,fail形成的集合結點有相同性質
for(int i=0;i<SIZE;i++)
{
if(u->next[i]!=NULL) //新結點,先構建fail,再加入隊列
{
if(u==root)u->next[i]->fail=root;//判斷當前是否是根,指向根
else
{ //不是根節點往回遍歷,處理和kmp相似
node *f=u->fail;
while(f!=root && f->next[i]==NULL)f=f->fail;
if(f->next[i]!=NULL)f=f->next[i];
u->next[i]->fail=f;
}
q.push(u->next[i]);
}
}
}
}
void query(char* s)
{
node *u=root;
int len=strlen(s);
for(int i=0;i<len;i++) //將s字符串一直向下匹配
{
int c=idx(s[i]);
while(u!=root && u->next[c]==NULL)u=u->fail;
if(u->next[c]!=NULL)
{
u=u->next[c];
node* temp=u;
while(temp!=NULL)
{
if(temp->exist)
{
vec[tot]=temp->exist;
tot++;
}
temp=temp->fail;
}
}
}
}
void del(node* rt)
{
if(rt==NULL)return;
for(int i=0;i<SIZE;i++)if(rt->next[i]!=NULL)del(rt->next[i]);
delete rt;
}
};
AC ac;
數組實現
struct node //node結構體
{
bool exist;
int next[55];
int fail;
node() //初始化
{
exist=0;
fail=0;
memset(next,0,sizeof(next));
}
};
struct AC
{
int tot;
node trie[505];
AC(){tot=1;memset(trie,0,sizeof(trie));}
void init(){tot=1;memset(trie,0,sizeof(trie));}
inline int idx(char c){return mc[c];} //size=26
void insert(char *s) //插入字符串建樹
{
int u=1;
int len=strlen(s);
for(int i=0;i<len;i++)
{
int c=idx(s[i]);
if(trie[u].next[c]==0)trie[u].next[c]=++tot;
u=trie[u].next[c];
}
trie[u].exist=true;
}
queue<int> q; //fail指針的構建,採用bfs的方式
void build()
{
while(!q.empty())q.pop();
q.push(1);
while(!q.empty())
{
int u=q.front();q.pop();//u指向fail位置具有的性質u也有,即fail形成的集合結點有相同性質!!
for(int i=0;i<SIZE;i++)
{
int v=trie[u].next[i];
if(v!=0) //對於每次存在的新結點,先構建fail,再加入隊列
{
if(u==1)trie[v].fail=1; //判斷當前是否是根結點,是的話下面的指向根
else
{ //不是根節點的情況往回遍歷,以下處理和kmp相似
int f=trie[u].fail;
while(f!=1 && trie[trie[f].next[i]].fail==0)f=trie[f].fail;
if(trie[f].next[i]!=1)f=trie[f].next[i];
trie[v].fail=f;
}
q.push(v);
}
}
}
}
};
AC ac;
struct node //node結構體
{
bool exist;
int next[26],fail;
node() //初始化
{
exist=0;
fail=0;
memset(next,0,sizeof(next));
}
};
struct AC
{
int tot;
node trie[505];
AC(){init();}
void init(){tot=1;memset(trie,0,sizeof(trie));}
inline int idx(char c){return mp[c];} //size=26
void insert(char *s) //插入字符串建樹
{
int u=0;
int len=strlen(s);
for(int i=0;i<len;i++)
{
int c=idx(s[i]);
if(trie[u].next[c]==0)trie[u].next[c]=tot++;
u=trie[u].next[c];
}
trie[u].exist=true;
}
queue<int> q; //bfs構建fail指針
void build()
{
while(!q.empty())q.pop();
for(int i=0;i<26;i++) //先處理根
{
int v=trie[0].next[i];
if(v==0)continue;
trie[v].fail=0;
q.push(v);
}
while(!q.empty())
{
int u=q.front();q.pop();//u指向fail位置具有的性質u也有!
for(int i=0;i<26;i++)
{
int v=trie[u].next[i];
if(v)
{
trie[v].fail=trie[trie[u].fail].next[i];
q.push(v);
}
else trie[u].next[i]=trie[trie[u].fail].next[i];//就是fail
}
}
}
void query(char *s)
{
}
};
AC ac;
後綴自動機
應用
1.dp
在ac自動機上面進行dp,需要注意的事情:
1.由於構建fail的時候,下層的結點具有與其fail結點共有的性質,我們需要將fail節點的性質向下賦值。
2.dp的思想:將自動機上面的所有結點當作一種狀態,記錄處於當前狀態的dp值,接下來可以進行字符串的轉移,利用fail指針進行的轉移來進行相關的dp。
#include<iostream>
#include<cstdio>
#include<queue>
#include<algorithm>
#include<string>
#include<vector>
#include<cstdlib>
#include<cmath>
#include<set>
#include<map>
#include<stack>
#include<iomanip>
#include<cstring>
#include<sstream>
#include<iomanip>
#include<fstream>
#include<fstream>
#define maxn 1005
#define maxm 105
typedef long long ll;
const int inf=1e8+7;
ll mod=998244353;
const double eps=1e-9;
using namespace std;
inline void read(int &x){scanf("%d",&x);}
inline void readll(ll &x){scanf("%lld",&x);}
int n;
map<char,int> mp;
char s[maxn];
ll f[maxn][maxn];
int SIZE=4;
struct node //node結構體
{
bool exist;
int next[4],fail;
node() //初始化
{
exist=0;
fail=0;
memset(next,0,sizeof(next));
}
};
struct AC
{
int tot;
node trie[1005];
AC(){init();}
void init(){tot=1;memset(trie,0,sizeof(trie));}
inline int idx(char c){return mp[c];} //size=26
void insert(char *s) //插入字符串建樹
{
int u=0,len=strlen(s);
for(int i=0;i<len;i++)
{
int c=idx(s[i]);
if(trie[u].next[c]==0)trie[u].next[c]=tot++;
u=trie[u].next[c];
}
trie[u].exist=true;
}
queue<int> q; //bfs構建fail指針
void build()
{
while(!q.empty())q.pop();
for(int i=0;i<SIZE;i++) //先處理根
{
int v=trie[0].next[i];
if(v==0)continue;
trie[v].fail=0;
q.push(v);
}
while(!q.empty())
{
int u=q.front();q.pop();
if(trie[trie[u].fail].exist==true)trie[u].exist=true;//u的fail的性質u也有
for(int i=0;i<SIZE;i++)
{
int v=trie[u].next[i];
if(v)
{
trie[v].fail=trie[trie[u].fail].next[i];
q.push(v);
}
else trie[u].next[i]=trie[trie[u].fail].next[i];//就是fail
}
}
}
ll query(char *str)
{
ll res=inf;
int len=strlen(str);
for(int i=0;i<=len;i++)
for(int j=0;j<tot;j++)
f[i][j]=inf;
f[0][0]=0;
for(int i=1;i<=len;i++)
{
for(int j=0;j<tot;j++)
{
for(int k=0;k<4;k++)
{
int v=trie[j].next[k];
if(trie[v].exist==0)
{
if(mp[str[i-1]]==k)f[i][v]=min(f[i][v],f[i-1][j]);
else f[i][v]=min(f[i][v],f[i-1][j]+1);
}
}
}
}
for(int i=0;i<tot;i++)res=min(res,f[len][i]);
if(res==inf)res=-1;
return res;
}
};
AC ac;
int main()
{
mp['A'] = 0;mp['C'] = 1;mp['G'] = 2;mp['T'] = 3;
int cas=0;
while(scanf("%d",&n)&& n)
{
ac.init();
for(int i=0;i<n;i++)
{
scanf("%s",s);
ac.insert(s);
}
ac.build();
scanf("%s",s);
printf("Case %d: %lld\n",++cas,ac.query(s));
}
return 0;
}