[SPOJ 1812]Longest Common Substring II

题目

        点这里看题目。

分析

        我们想要建立一个只包含多个串的公共子串的后缀自动机。最简单的,先建立一个串的后缀自动机。
        然后考虑如何进行扩展。在两个串的情况下,我们可以直接把另一个串放到后缀自动机上面跑,中途得到答案。在多个串的情况下显然就不会这么简单了。
        考虑每一个串都放到后缀自动机上面跑一跑。对于每一个点,我们就可以存下来它在当前这个串中以它结尾的串的最大长度。一次遍历之后,答案就是这些最大长度中间的最大值。
        要都是公共的情况,只需要每次遍历完之后,将每个点每次的最大长度取最小值(对于每一个点,\(s_1\)遍历出来最大长度为\(a\)\(s_2\)\(b\)\(a>b\)的话,\(b\)肯定是\(a\)的子串,也就既是\(s_1\)也是\(s_2\)的子串)之后,总体再取最大值即可。
        需要注意的是,如果一个串的得到的最大长度比它父亲的在后缀自动机上的最大长度长,那么就要更新父亲的得到的最大长度(它的父亲是它的后缀)。这个可以在每次遍历结束后用一次拓扑序 DP 更新完成。
        在两个串的时候不用这么做是因为,如果不能更新,就不能更新,如果可以更新也没有什么意义(答案不会更大)。

代码

#include <cstdio>
#include <cstring>

const int MAXN = 100005;

const int MAXCNT = 15;

template<typename _T>
void read( _T &x )
{
	x = 0; char s = getchar();int f = 1;
	while( s < '0' || '9' < s ) { f = 1; if( s == '-' ) f = -1; s = getchar(); }
	while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + s - '0', s = getchar(); }
	x *= f;
}

template<typename _T>
void write( _T x )
{
	if( x < 0 ) { putchar( '-' ), x = -x; }
	if( 9 < x ) { write( x / 10 ); }
	putchar( x % 10 + '0' );
}

template<typename _T>
_T MAX( const _T a, const _T b )
{
	return a > b ? a : b;
}

template<typename _T>
_T MIN( const _T a, const _T b )
{
	return a < b ? a : b;
}

int seq[MAXN << 1], cnt[MAXN << 1];
char str[MAXCNT][MAXN];
int l[MAXCNT];
int ch[MAXN << 1][26], fa[MAXN << 1], mx[MAXN << 1], nmx[MAXN << 1], mn[MAXN << 1];
int K, rt, tot, lst;

void copy( const int a, const int b ) { fa[a] = fa[b], mx[a] = mx[b], memcpy( ch[a], ch[b], sizeof ch[b] ); }

void expand( const char c )
{
	int x = c - 'a', p = lst, cur = ++ tot;
	mx[cur] = mx[lst] + 1, lst = cur;
	while( p && ! ch[p][x] ) ch[p][x] = cur, p = fa[p];
	if( ! p ) { fa[cur] = rt; return ; }
	int q = ch[p][x];
	if( mx[q] == mx[p] + 1 ) { fa[cur] = q; return ; }
	int nq = ++ tot; copy( nq, q );
	mx[nq] = mx[p] + 1, fa[cur] = fa[q] = nq;
	while( p && ch[p][x] == q ) ch[p][x] = nq, p = fa[p];
}

void topo()
{
	for( int i = 1 ; i <= tot ; i ++ ) cnt[mx[i]] ++;
	for( int i = 1 ; i <= tot ; i ++ ) cnt[i] += cnt[i - 1];
	for( int i = tot ; i ; i -- ) seq[cnt[mx[i]] --] = i;
}

//基排得到拓扑序

int main()
{
	while( ~ scanf( "%s", str[++ K] + 1 ) ) l[K] = strlen( str[K] + 1 ); K --;
	rt = lst = ++ tot;
	for( int i = 1 ; i <= l[1] ; i ++ ) expand( str[1][i] );
	for( int i = 1 ; i <= tot ; i ++ ) mn[i] = mx[i];  //一个点的公共的最大长度不会超过后缀自动机上面的最大长度
	topo();
	int p, len, x;
	for( int j = 2 ; j <= K ; j ++ )
	{
		len = 0, p = rt;
		for( int i = 1 ; i <= l[j] ; i ++ )
		{
			x = str[j][i] - 'a';
			while( p && ! ch[p][x] ) p = fa[p], len = mx[p];
			if( p ) len ++, p = ch[p][x], nmx[p] = MAX( nmx[p], len );
			else len = 0, p = rt;
		}
		for( int i = tot, np ; i ; i -- )
		{
			np = seq[i]; if( fa[np] ) nmx[fa[np]] = MAX( nmx[fa[np]], MIN( mx[fa[np]], nmx[np] ) );    //对父亲更新
			mn[np] = MIN( mn[np], nmx[np] ), nmx[np] = 0;  //记得nmx要清零
		}
	}
	int res = 0;
	for( int i = 1 ; i <= tot ; i ++ ) res = MAX( res, mn[i] );  //全局取max得到答案
	write( res ), putchar( '\n' ); 
	return 0;
}

猜你喜欢

转载自www.cnblogs.com/crashed/p/12898697.html