题解 P3796 【【模板】AC自动机(加强版)】
I_AM_HelloWord · · 题解
还是属于AC自动机的裸题。而AC自动机的裸题,先把模板打好后,基本上改的就是对标记点的处理。
这里就是对AC自动标记点处理的简单修改,即把匹配到的标记点都计数一下,然后求计数最大的那些标记点就行了。
还是不太不懂AC自动记得萌新们可以戳:http://blog.csdn.net/no1\_terminator/article/details/77725530
讲的非常清楚哟!
这里就简单的讲一下吧:
f[u]=v表示从根到u号节点的这个字符串的某个后缀与从根到v号节点的这个字符串是等效的(即完全相等的)
last[u]=v表示从u开始不断沿着失配边跳到的第一个是标记点的端点v,那么我们再匹配是就无需沿着f跳,而是沿着last跳
考虑f的递推。我们设当前节点为u,其一个孩子ch[u][c]=v,k表示u沿着f边跳一次对应的点,即k=f[u],
那么如果u不是根节点,f[v]=ch[k][c],即沿着k再向下走一个c字符,这时两个字符串还是相等的对不对。
如果u是根节点那就没什么好说的了,f[v]=0。
在此条件下,last就非常好递推了,,那么如果f[v]是标记点,那么last[v]=f[v],
否则last[v]=last[f[v]]。
考虑到这是一个Trie树上的递推,所以我们用BFS搞一搞就好了。
参考代码:
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#define reset(a) memset((a),0,sizeof((a)))
#define REP(i,a,b) for (int i=(a);i<=(b);i++)
using namespace std;
const int N=1e6+10;
const int M=151*71;
char s[151][71];
char T[N];
int m;
struct Aho_Corasick_Automaton{
int NodeCnt,f[M],ch[M][26],cnt[M],val[M],last[M];
void clear(){
reset(f);reset(ch);reset(cnt);reset(val);reset(last);
NodeCnt=0;
}
void insert(char *s,int index){
int u=0,n=strlen(s);
REP(i,0,n-1){
int c=s[i]-'a';
if (!ch[u][c])ch[u][c]=++NodeCnt;
u=ch[u][c];
}
val[u]=index;
}
void getFail(){
queue<int> Q;
Q.push(0);
while (!Q.empty()){
int u=Q.front(),k=f[u];Q.pop();
REP(c,0,25){
int v=ch[u][c];
if (!v){ch[u][c]=ch[k][c];continue;}
f[v]=u?ch[k][c]:0;
last[v]=val[f[v]]?f[v]:last[f[v]];
Q.push(v);
}
}
}
void query(char *T){
int u=0,n=strlen(T),res=0;
REP(i,0,n-1){
int c=T[i]-'a';
u=ch[u][c];
if (val[u])cnt[val[u]]++;
int v=last[u];
while (v){
if (val[v])cnt[val[v]]++;
v=last[v];
}
}
REP(i,1,m)res=max(res,cnt[i]);
printf("%d\n",res);
REP(i,1,m)if (cnt[i]==res)printf("%s\n",s[i]);
return;
}
}AC;
int main(){
while (scanf("%d",&m)==1 && m){
AC.clear();
REP(i,1,m){
scanf("%s",s[i]);
AC.insert(s[i],i);
}
AC.getFail();
scanf("%s",T);
AC.query(T);
}
return 0;
}