spoj1811 Longest Common Substring
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <cmath> 5 #include <algorithm> 6 #define maxn 500005 7 #define maxl 250005 8 using namespace std; 9 10 int n,m,ans,last,len,tot,root,son[maxn][26],fa[maxn],dist[maxn]; 11 char s1[maxl],s2[maxl]; 12 struct Tsegment{ 13 void prepare(){last=tot=root=1;} 14 int newnode(int x){dist[++tot]=x;return tot;} 15 void add(int x){ 16 int p=last,np=newnode(dist[p]+1); last=np; 17 for (;p&&!son[p][x];p=fa[p]) son[p][x]=np; 18 if (p==0) fa[np]=root; 19 else{ 20 int q=son[p][x]; 21 if (dist[p]+1==dist[q]) fa[np]=q; 22 else{ 23 int nq=newnode(dist[p]+1); 24 memcpy(son[nq],son[q],sizeof(son[q])); 25 fa[nq]=fa[q],fa[q]=fa[np]=nq; 26 for (;p&&son[p][x]==q;p=fa[p]) son[p][x]=nq; 27 } 28 } 29 } 30 }SAM; 31 int main(){ 32 scanf("%s",s1+1),n=strlen(s1+1); 33 scanf("%s",s2+1),m=strlen(s2+1); 34 SAM.prepare(); 35 for (int i=1;i<=n;i++) SAM.add(s1[i]-'a'); 36 ans=0,len=0,last=root; 37 for (int i=1;i<=m;i++){ 38 int x=s2[i]-'a'; 39 if (son[last][x]) len++,last=son[last][x]; 40 else{ 41 for (;last&&!son[last][x];) last=fa[last]; 42 if (last==0) len=0,last=root; 43 else{ 44 len=dist[last]+1,last=son[last][x]; 45 } 46 } 47 ans=max(ans,len); 48 } 49 printf("%d\n",ans); 50 return 0; 51 }
题目链接:http://begin.lydsy.com/JudgeOnline/problem.php?id=2796
题目大意:给定两个字符串,长度<=2.5*10^5,询问这两个字符串的最长公共子串的长度,这题以前用后缀数组写过,比较基础,这次是用SAM写的,比较坑爹。
做法:最近学习了后缀自动机,讲讲我的理解:
后缀自动机可以识别一个字符串中的所有子串,写过AC自动机的同学都知道Trie树,如果按照那种方法建树的话,空间复杂度为n^2,但是我们发现有很多重复的状态,我们会发现,有很多个子串的右端点集合完全相同,那么这些字符串向后匹配的能力是相同的,故可将其缩成一个状态。
我先介绍几个性质:
1.right集合要么没有交集,要么真包含,用反证法易得。
2.对于某一个right集合中字符串,其长度有一个区间,即为【min,max】,若大于这个长度区间,|right|会减小,反之增大。
3.由于性质1,我们可以发现利用right集合能建立一棵树,满足:父亲的right集合是真包含儿子节点right集合中max最大的 ,且满足父亲的max+1=儿子的min。这三条性质在建立后缀自动机的时候有用。
如何建立后缀自动机呢?
建立过程比较麻烦,大家画个图理解理解吧,orzclj……
具体看代码。。。。。细节太多。。
这于这题的做法:
对第一个字符串建立SAM,第二个字符串在SAM上匹配即可,若失配,就跳fa,因为fa的right集合真包含于自己的right集合,这样缩短字符串长度,却能增加后续匹配的可能性,及时更新答案即可。
后缀自动机。