spoj - Longest Common Substring(后缀自动机模板题)
Longest Common Substring
题意
求两个串的最长公共子串。
分析
第一个串建后缀自动机,第二个串在自动机上跑,对于自动机上的结点(状态)而言,它所代表的最大长度为根结点到当前结点的长度,而它的前继结点的串一定是这个结点串的后缀串(或空串)。
匹配过程中一旦失配,自动机上的结点找它的前继结点,继续向后匹配即可。
code
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 250005;
char s[MAXN];
/* 性质:
① 从root到任意结点p的每条路径上的字符组成的字符串,都是当前串t的子串
② 因为满足性质一,所以如果当前结点p是可以接收新后缀的结点,那么从root到任意结点p的每条路径上的字符组成的字符串,都是必定是当前串t的后缀
③ 如果结点p可以接收新的后缀,那么p的fa指向的结点也可以接收后缀,反过来就不行
从 root 出发的任何子串最终都会到达一个合法状态,而非子串最终都会“无路可走”
*/
struct SAM {
int ch[MAXN << 1][26];
int fa[MAXN << 1]; // 前继结点,如果当前结点可以接受某个后缀那么它的前继结点也可以接受
int len[MAXN << 1]; // 从根结点到该结点的最大距离,这个状态(结点)代表的串长度区间 (len[fa], len]
int cnt, last;
void init() {
memset(ch, 0, sizeof ch);
memset(fa, 0, sizeof fa);
last = cnt = 1; // root节点为 1 ,所以添加的字符从 2 开始
}
void add(int c) {
int p = last, np = last = ++cnt;
len[np] = len[p] + 1;
while(!ch[p][c] && p) {
ch[p][c] = np;
p = fa[p];
}
if(p == 0) fa[np] = 1;
else {
int q = ch[p][c];
if(len[p] + 1 == len[q]) { // p、q之间无其他结点
fa[np] = q;
} else { // np的加入导致前面某个结点的状态发生改变
int nq = ++cnt;
len[nq] = len[p] + 1;
memcpy(ch[nq], ch[q], sizeof ch[q]);
fa[nq] = fa[q];
fa[q] = fa[np] = nq;
while(ch[p][c] == q && p) {
ch[p][c] = nq;
p = fa[p];
}
}
}
}
} sam;
int main() {
scanf("%s", s);
int len = strlen(s);
sam.init();
for(int i = 0; i < len; i++) {
sam.add(s[i] - 'a');
}
scanf("%s", s);
len = strlen(s);
int p = 1;
int ans = 0;
int c = 0;
for(int i = 0; i < len; ) {
while(sam.ch[p][s[i] - 'a']) {
p = sam.ch[p][s[i] - 'a'];
i++;
c++;
}
ans = max(ans, c);
if(!c) i++;
else {
p = sam.fa[p];
c = sam.len[p];
}
}
printf("%d\n", ans);
return 0;
}