传送门
对于给定的串建广义后缀自动机,然后标记每个点所代表的字符串属于多少串,这个标记方式和之前的那道最长公共子串的匹配方式有些像,可以这样理解,枚举这个字符串的前缀,然后将这个前缀的所有后缀都标记上,就是将所有子串都标记了。这个也是后面才理解到的,SAM 的性质实在太神奇了。
然后匹配就不用细说了,就是沿着 DAG 跑,如果字符串没跑完 DAG 就跑不动了,答案当然就是 0,如果跑完了,答案就是目前 DAG 节点上的标记值。
这个代码的广义SAM写法有些问题,应该判断想插入的节点是否存在的问题,之后有正确的写法。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1e5+10;
int n,m,tlen;
string s[N],t;
struct SuffixAutoMachine{
int last=1,tot=1,fa[N*2],ch[N*2][26],len[N*2],f[N*2],vis[N*2];
int newnode(int x){fa[++tot]=fa[x];len[tot]=len[x];memcpy(ch[tot],ch[x],sizeof(ch[tot]));return tot;}
void append(int c){
int p=last,np=last=newnode(0);
len[np]=len[p]+1;
for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=np;
if(!p) {fa[np]=1;return;}
int q=ch[p][c];
if(len[q]==len[p]+1) {fa[np]=q;return;}
int nq=newnode(q);len[nq]=len[p]+1;
fa[q]=fa[np]=nq;
for(;p&&ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
}
void insert(string s){
last=1;
for(int i=0;i<s.size();i++) append(s[i]-'a');
}
void update(int p,int id){
//这里就是在枚举p所代表的子串的后缀,vis可以加快速度
for(;p&&vis[p]<id;p=fa[p]) f[p]++,vis[p]=id;
}
void match(string s){
bool flag=true;
int p=1;
for(int i=0;i<s.size();i++)
if(ch[p][s[i]-'a']) p=ch[p][s[i]-'a'];
else {flag=false;break;}
printf("%d
",flag?f[p]:0);
}
}sam;
int main(){
ios::sync_with_stdio(false);cin.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>s[i],sam.insert(s[i]);
for(int i=1;i<=n;i++)
for(int j=0,p=1;j<s[i].size();j++)
//这个操作其实就是枚举s的所有前缀,扔到update里去枚举前缀的后缀
sam.update(p=sam.ch[p][s[i][j]-'a'],i);
for(int i=1;i<=m;i++){
cin>>t;
sam.match(t);
}
return 0;
}