传送门
想用后缀数组和后缀自动机来搞这道题,都不成功,不是卡内存就是卡时间,后缀数组有一招二分后缀,但是各种条件不好写,所以还是憋着复习了一下ac自动机。
学了后缀自动机之后对ac自动机的fail树也更加理解了,一个点的匹配次数其实是fail树上它的子树的匹配次数之和,这和后缀自动机的endpos的原理一模一样。
这道题直接建树,然后跑主串,匹配的时候不用沿着fail回跳,只需在当前节点+1,然后最后按节点深度排序,就是拓扑序列,统计子树和就可以了,对于每个询问输出对应节点的值。
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int n,pos[N];
char s[N*10],t[N];
struct ACAutomaton{
int tot,tr[N][26],fa[N],dep[N],val[N],a[N],c[N];
int newnode(){val[++tot]=0;memset(tr[tot],0,sizeof(tr[tot]));return tot;}
int insert(){
int n=strlen(t+1),p=0;
for(int i=1;i<=n;i++){
if(!tr[p][t[i]-'a']) tr[p][t[i]-'a']=newnode();
dep[tr[p][t[i]-'a']]=dep[p]+1;
p=tr[p][t[i]-'a'];
}
return p;
}
void getfail(){
queue<int> que;
for(int i=0;i<26;i++) if(tr[0][i]) que.push(tr[0][i]),fa[tr[0][i]]=0;
while(!que.empty()){
int u=que.front();que.pop();
for(int i=0;i<26;i++)
if(tr[u][i]) fa[tr[u][i]]=tr[fa[u]][i],que.push(tr[u][i]);
else tr[u][i]=tr[fa[u]][i];
}
}
void solve(){
int n=strlen(s+1);
for(int i=1,p=0;i<=n;i++){
int c=s[i]-'a';
while(p&&!tr[p][c]) p=fa[p];
p=tr[p][c];
if(p) val[p]++;
}
for(int i=1;i<N;i++) c[i]=0;
for(int i=1;i<=tot;i++) c[dep[i]]++;
for(int i=1;i<N;i++) c[i]+=c[i-1];
for(int i=tot;i>=1;i--) a[c[dep[i]]--]=i;
for(int i=tot;i>=1;i--) val[fa[a[i]]]+=val[a[i]];
}
}ac;
int main(){
ac.tot=0;memset(ac.tr[0],0,sizeof(ac.tr[0]));
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%s",t+1);
pos[i]=ac.insert();
}
ac.getfail();
scanf("%s",s+1);
ac.solve();
for(int i=1;i<=n;i++) printf("%d
",ac.val[pos[i]]);
return 0;
}