洛谷 SP8093 JZPGYZ

传送门
对于给定的串建广义后缀自动机,然后标记每个点所代表的字符串属于多少串,这个标记方式和之前的那道最长公共子串的匹配方式有些像,可以这样理解,枚举这个字符串的前缀,然后将这个前缀的所有后缀都标记上,就是将所有子串都标记了。这个也是后面才理解到的,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;
}
原文地址:https://www.cnblogs.com/BakaCirno/p/12670833.html