【题解】「2017 山东一轮集训 Day5」字符串
\(\text{Solution:}\)
考虑只有一个串的时候,答案就是所有本质不同子串。
那么多个串,自然联想到把它们拼在一起建立 SAM.
但是如果这样,我们匹配到的一定是拼接起来的串上的连续一部分,并不是全部的答案,因为这里除了每一个串内它是可以断开的。
那只有串内需要连续,我就在串内建立 SAM.
考虑如何将边转移?我们对于每一个字符显然需要连接到下一个最近的和它相同字符的地方。那我们可以用子序列自动机,或者说维护出每一个点处下一个距离他最近的相同字符的点在哪里,然后进行连边。
于是这样我们就得到了一个整体的 DAG,它满足:我们在寻找完某一个串后可以跳跃到下一个严格在它后面的串,同时保证可以断开跳跃,且后面的串没办法跳回前面,且每一个串内选择的一定是连续子串。
那我们就可以在上面 \(dp\) 了。注意包含空串。
#include<bits/stdc++.h>
using namespace std;
const int N=2e6+10;
const int mod=1e9+7;
inline int Add(int x,int y){return (x+y)%mod;}
inline int Mul(int x,int y){return 1ll*x*y%mod;}
namespace SAM{
int len[N],pa[N],f[N],ch[N][26],last=0,tot=0,root;
void insert(const int &c){
int p=last;
int np=++tot;
last=tot;len[np]=len[p]+1;
for(;p&&!ch[p][c];p=pa[p])ch[p][c]=np;
if(!p)pa[np]=root;
else{
int q=ch[p][c];
if(len[q]==len[p]+1)pa[np]=q;
else{
int nq=++tot;
len[nq]=len[p]+1;
pa[nq]=pa[q];pa[q]=pa[np]=nq;
memcpy(ch[nq],ch[q],sizeof ch[q]);
for(;p&&ch[p][c]==q;p=pa[p])ch[p][c]=nq;
}
}
}
void dfs(const int &x){
if(f[x])return;
f[x]=1;
for(int i=0;i<26;++i){
int v=ch[x][i];
if(!v)continue;
dfs(v);
f[x]=Add(f[x],f[v]);
}
}
}
using namespace SAM;
int rt[N],n;
string s[N];
int nxtpos[26];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i)cin>>s[i];
for(int i=1;i<=n;++i){
rt[i]=last=root=++tot;
for(int j=0;j<(int)s[i].size();++j)insert(s[i][j]-'a');
}
rt[n+1]=tot+1;
for(int i=n;i>=1;--i){
for(int j=rt[i];j<rt[i+1];++j){
for(int k=0;k<26;++k)
if(!ch[j][k])
ch[j][k]=nxtpos[k];
}
for(int j=0;j<26;++j)nxtpos[j]=ch[rt[i]][j];
}
dfs(1);
printf("%d\n",f[1]);
return 0;
}