我们把所有单词”拼”起来成为一个串,两个单词之间用分隔符隔开(我的代码里是’{‘),那么要求的就是每个单词在这个拼起来的串中的出现次数。
那么这个就是后缀自动机统计子串出现次数的经典问题。
一个状态的Right集大小就是以它为起点,以一个终态为终点的路径条数(如果这个点本身就是终态,那么这一个点也算一条合法路径),那么统计这个可以用拓扑排序后DP解决(从FHQ博客上看的),我用的是记忆化搜索,感觉好写一点…
貌似还可以用Parent树来搞,不过我太弱了不会写OLZ…
写的时候忘记更新nq节点的nq->l了,导致出错,下次一定要注意.
AC code:
#include <cstdio>
#include <cstring>
const int K=27;
const int N=210;
const int L=2100010;
int n,cnt,ls;
int len[N],head[N];
bool vis[L];
char s[L],t[L];
struct nod{
int l,w,num;
nod *pr,*ch[K];
}pool[L];
struct SAM{
nod *root,*last;
SAM(){
root=last=&pool[0];
}
void extend(int x){
nod *p=last,*np=&pool[++cnt];
for(last=np,np->num=cnt,np->l=p->l+1;p&&(!p->ch[x]);p->ch[x]=np,p=p->pr) ;
if(!p) np->pr=root;
else{
nod *q=p->ch[x];
if(p->l+1==q->l) np->pr=q;
else{
nod *nq=&pool[++cnt];
*nq=*q;nq->l=p->l+1;nq->num=cnt;q->pr=np->pr=nq;
for(;p&&p->ch[x]==q;p->ch[x]=nq,p=p->pr) ;
}
}
}
void dfs(nod *p){
vis[p->num]=1;
for(int i=0;i<K;i++){
if(!p->ch[i]) continue;
if(!vis[p->ch[i]->num]) dfs(p->ch[i]);
p->w+=p->ch[i]->w;
}
}
void build(){
for(nod *i=last;i!=root;i=i->pr) i->w=1;
dfs(root);
}
void match(int L,int R){
nod *p=root;
for(int i=L;i<=R;p=p->ch[s[i++]-'a']) ;
printf("%d\n",p->w);
}
}AM;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%s",t);
len[i]=strlen(t);
strcat(s,t);
int l=strlen(s);
s[l]='{';
s[l+1]='\0';
head[i+1]=l+1;
}
ls=strlen(s);
for(int i=0;i<ls;i++) AM.extend(s[i]-'a');
AM.build();
for(int i=1;i<=n;i++) AM.match(head[i],head[i]+len[i]-1);
return 0;
}