傳送門
原串建立後綴自動機。用輸入的串在上面跑DP。
表示已經確定了前個氨基酸,並且當前在後綴自動機的節點上 的方案數。
那麼我們假設之前個氨基酸已經確定,而現在輸入了一個串,我們考慮暴力從自動機上的每個節點開始按照往下走。
假設當前枚舉到節點。如果走不下去了就退出。然而如果走完了,到達節點,就表示接上之後可以和原串匹配。
那麼如果可以接上,轉移方程就是
得到每個自動機上每個節點(由k個串組合得到)的方案數後,乘上endpos集合的大小即可(代碼中對應right)
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e4+10;
const int mod=1e9+7;
char N[maxn];int f[105][maxn<<1],sum[maxn<<1],t[maxn<<1];
int last,sz,k,n,T,ans=0;
struct node{int len,link,right,nxt[26];}st[maxn<<1];
inline int add(int x,int y){return (x+y>=mod)?(x+y-mod):(x+y);}
inline int mul(int x,int y){return (ll)x*y%mod;}
inline void init(){last=sz=0,st[0].right=st[0].link=-1,st[0].len=0;}
inline void build(int c){
int cur=++sz,p=last;
st[cur].len=st[last].len+1,st[cur].right=1;
for(;p!=-1&&!st[p].nxt[c];p=st[p].link)
st[p].nxt[c]=cur;
if(p==-1) st[cur].link=0;
else{
int q=st[p].nxt[c];
if(st[p].len+1==st[q].len) st[cur].link=q;
else{
int clone=++sz;st[clone]=st[q];
st[clone].right=0,st[clone].len=st[p].len+1;
for(;p!=-1&&st[p].nxt[c]==q;p=st[p].link)
st[p].nxt[c]=clone;
st[cur].link=st[q].link=clone;
}
}last=cur;
}
inline void calc_right(){
memset(sum,0,sizeof(sum));
for(int i=1;i<=sz;++i) sum[st[i].len]++;
for(int i=1;i<=n;++i) sum[i]+=sum[i-1];
for(int i=sz;i>=1;i--) t[sum[st[i].len]--]=i;
for(int i=sz;i>=1;i--)
st[st[t[i]].link].right+=st[t[i]].right;
}
int main(){
scanf("%d%s",&k,N),n=strlen(N),init();
for(int i=0;i<n;++i) build(N[i]-'A');
f[0][0]=1,calc_right();
for(int i=1;i<=k;++i){
scanf("%d",&T);
while(T--){
scanf("%s",N),n=strlen(N);
for(int p=0;p<=sz;++p){
int now=p;
for(int q=0;q<n;++q){
if(st[now].nxt[N[q]-'A'])
now=st[now].nxt[N[q]-'A'];
else{now=-1;break;}
}if(now!=-1) f[i][now]=add(f[i][now],f[i-1][p]);
}
}
}
for(int i=1;i<=sz;++i) ans=add(ans,mul(st[i].right,f[k][i]));
cout<<ans<<'\n';
}