AC自动机
trie+kmp
构建fail指针
朴素思想:
当前节点为p,之前已求得fail[p]
trie[p,c]是p 通过字符c指向的 一个子节点,现要求fail[ trie[p,c] ],即fail[x]
若trie[fail[p],c]存在,则fail[x]=trie[fail[p],c], 即p的子节点的fail = p的fail的子节点
否则,让p一直跳fail指针,fail[x]=trie[fail[fail[p]],c],直到存在 / 根节点
fail路径压缩后,可优化为trie图
匹配查询 query
其实就是,在trie上查询s串的每个前缀
在字典树上走一遍s串,每次累加当前后缀能匹配的个数 (通过跳fail实现)
fail树上dfs / 拓扑序优化: O(n)
query_mx时每次都跳fail到根节点,最坏为 O(n*m)
fail树:所有fail指针连成边,是一棵树
即,每个节点的fail指针指向fa,节点0作根
优化:只在fail树的"末端"节点+1(差分思想),再dfs / 按拓扑序转移
例题
P3796 【模板】AC自动机(加强版)
P3808 【模板】AC自动机(简单版)
P5357 【模板】AC自动机(二次加强版)
代码
const int N=150+5;
int n;
char s[N][75], str[2000010];
int tr[N*70][26],idx;//总字符为 N
int ed[N*70], fail[N*70];
int id[N*70];//字符串末端的trie节点对应的 id ( id和trie节点相互映射 )
int mp[N*70];//id 映射为字符串末端的 trie节点
int cnt[N*70];
vector<int> mxi;
namespace AC{
void insert(char s[],int id){
int p=0;
for(int i=0;s[i];++i){
int ch=s[i]-'a';
if(tr[p][ch]==0) tr[p][ch]=++idx;
p=tr[p][ch];
}
// ed[p]+=1;
ed[p]=id; //1、trie节点映射为模板串id (前提是不存在相同的模板串)
mp[id]=p; //2、id 映射为 trie节点 (可以重复相同)
}
void build(){ //bfs实现,对trie中每个节点,都逐层求fail指针 (fail指针一定指向层数比自己小的)
queue<int> q;
fu(i,0,25) if(tr[0][i]) q.push(tr[0][i]);
while(q.size()){
int p=q.front(); q.pop();
fu(i,0,25){
if(tr[p][i]) //p的子节点的fail = p的fail的子节点
fail[tr[p][i]]=tr[fail[p]][i], q.push(tr[p][i]);
else tr[p][i]=tr[fail[p]][i]; //若子节点不存在,让p跳fail
}
}
}
int query_cnt(char s[]){ //(文本串信息)求多少个模板串 在文本串s中出现过
int p=0, res=0;//
for(int i=0;s[i];++i){//在trie上走一遍s串
p=tr[p][s[i]-'a'];
for(int j=p; j&&ed[j]!=-1; j=fail[j])
res+=ed[j], ed[j]=-1; //每个模板串只计数一次,走过的置为-1 (当然置0,每次走到根也可,复杂度较大)
}
return res;
}
int query_mx(char s[]){//(模板串信息)求在文本串中 出现次数最多的模板串,及其次数
int p=0, mx=-1;
for(int i=0;s[i];++i){
p=tr[p][s[i]-'a'];
for(int j=p; j; j=fail[j]) {//这里最坏 O(n*m)复杂度。。可fail树上dfs优化
cnt[j]+=1;
//if(ed[j]==0) continue;
}
}
fu(i,0,idx) if(ed[i]) mx=max(mx,cnt[i]); //枚举0~idx所有节点
fu(i,0,idx) if(ed[i] && cnt[i]==mx) mxi.pb(ed[i]); //i是trie节点编号,ed[i]才是字符串编号
return mx;
}
}
vector<int> g[N*70];
void dp(int x){
for(auto &y:g[x]){
dp(y);
cnt[x]+=cnt[y];
}
}
int solve(){
fu(i,0,idx) g[i].clear();
fu(i,0,idx) if(i!=0) g[fail[i]].pb(i);//fail[i]作fa
dp(0);
int mx=-1;
fu(i,0,idx) if(ed[i]) mx=max(mx,cnt[i]);
fu(i,0,idx) if(ed[i] && cnt[i]==mx) mxi.pb(ed[i]);
return mx;
}
int query_mx_dp(char s[]){//fail树上dfs
int p=0;
for(int i=0;s[i];++i){
p=tr[p][s[i]-'a'];
cnt[p]+=1; //差分思想,节点p到根都 +1 ,dfs求最终贡献
}
return solve();
}
int Work(){
idx=0;
mxi.clear();
fu(i,0,n*70) ms(tr[i],0), ed[i]=fail[i]=0, cnt[i]=0;
fu(i,1,n) scanf("%s",s[i]), AC::insert(s[i],i);
AC::build();
scanf("%s",str);
// printf("%d\n", AC::query_cnt(str) );
// printf("%d\n", AC::query_mx(str) );
printf("%d\n", query_mx_dp(str));
for(auto x:mxi) printf("%s\n",s[x]);
return 0;
}
int main(){
while(~scanf("%d",&n) && n) Work();
return 0;
}