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 }
View Code

题目链接: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集合,这样缩短字符串长度,却能增加后续匹配的可能性,及时更新答案即可。

后缀自动机。

posted @ 2016-05-31 15:36  oyzx~  阅读(232)  评论(2编辑  收藏  举报