@spoj - lcs@ Longest Common Substring


@description@

求两个仅由小写字母构成的字符串的最长公共子串。

input
两行。每行一个不超过 250000 个小写字母的字符串。

output
最长公共子串长度。如果没有,输出 0。

sample input
alsdfkjfjkdsal
fdjskalajfkdsla
sample output
3

@solution@

其实有一个不涉及任何字符串算法的简单算法。我们发现公共子串的长度是单调的(即大公共串的子串也是公共串),因此我们可以对第二个串进行滑动窗口。只是这个算法需要判断一个串是否为另一个串的子串,所以很慢。
但是这个算法可以帮助我们理解后缀自动机的过程。

后缀自动机中,我们将所有结点都设置为可接受的结点,那么可接受的串就是所有的子串。
因此我们可以将第二个串放到第一个串建成的后缀自动机上跑。如果跑不动了就沿着 fa 跳。
可以发现这个过程其实跟滑动窗口特别像。跑后缀自动机就是移动右端点,沿着 fa 跳就是移动左端点。

@accepted code@

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 250000;
struct sam{
	sam *ch[26], *fa; int mx;
}pl[2*MAXN + 5], *tcnt, *root, *lst;
void init() {
	lst = tcnt = root = &pl[0];
	for(int i=0;i<26;i++)
		root->ch[i] = NULL;
	root->fa = NULL, root->mx = 0;
}
sam *newnode() {
	tcnt++;
	for(int i=0;i<26;i++)
		tcnt->ch[i] = NULL;
	tcnt->fa = NULL, tcnt->mx = 0;
	return tcnt;
}
void sam_extend(int x) {
	sam *cur = newnode(), *p = lst;
	cur->mx = lst->mx + 1; lst = cur;
	while( p && !p->ch[x] )
		p->ch[x] = cur, p = p->fa;
	if( !p )
		cur->fa = root;
	else {
		sam *q = p->ch[x];
		if( q->mx == p->mx + 1 )
			cur->fa = q;
		else {
			sam *cne = newnode();
			(*cne) = (*q); cne->mx = p->mx + 1;
			q->fa = cur->fa = cne;
			while( p && p->ch[x] == q )
				p->ch[x] = cne, p = p->fa;
		}
	}
}
char S[MAXN + 5], T[MAXN + 5];
int main() {
	init();
	scanf("%s%s", S, T);
	int lenS = strlen(S), lenT = strlen(T);
	for(int i=0;i<lenS;i++)
		sam_extend(S[i] - 'a');
	int ans = 0, res = 0; sam *nw = root;
	for(int i=0;i<lenT;i++) {
		while( nw && !nw->ch[T[i] - 'a'] )
			nw = nw->fa;
		if( !nw ) nw = root;
		else res = min(res, nw->mx), nw = nw->ch[T[i] - 'a'], res++;
		ans = max(ans, res);
	}
	printf("%d\n", ans);
}

@details@

注意一下每次去更新答案的不是 nw->mx 而是我们中途一直维护的一个变量 res,至于这个变量的意义可以像我说的一样用滑动窗口的方式去理解。

posted @ 2019-01-10 19:11  Tiw_Air_OAO  阅读(166)  评论(0编辑  收藏  举报