【luogu SP1812】LCS2 - Longest Common Substring II

LCS2 - Longest Common Substring II

题目链接:luogu SP1812

题目大意

给你多个字符串,要你求它们的最长公共子串。

思路

首先我们要知道如果只有两个字符串如何用 SAM 来做:
——>在这里看<——

接着我们考虑如何从两个字符串扩展到 n 个字符串。
我们考虑沿用之前的思路,拿一个字符串建 SAM,然后拿其它的放到上面跑。
我们可以考虑求出每个跑的字符串的 m a x n i maxn_i maxni 数组,表示 SAM 上的 i i i 点结尾形成的子串和你现在字符串的匹配长度。(从后往前匹配)

你会以为只要之前取 max 值的地方变成 maxn[now] = max(maxn[now], nowdis)就好了,但其实不是。
你想一下你匹配不了的时候会干什么,会跳 f a i fa_i fai,那为什么可以跳?
那是因为它们之间只是前面减去了字符,那你会想到,如果它们两个都可以匹配,那肯定是选 i i i 而不是 f a i fa_i fai,然后你就会发现,按你这么搞的话就只有 i i i 会被统计, f a i fa_i fai 则并不会。
那要怎么办呢?

观察到时 i i i f a i fa_i fai,你考虑先像上面一样搞,然后最后按逆拓扑序枚举 i i i,把 m a x n f a i = max ⁡ { m a x n f a i , m a x n i } maxn_{fa_i}=\max\{maxn_{fa_i},maxn_i\} maxnfai=max{maxnfai,maxni}
但你会发现它还是有锅,你这样直接赋值,说不定你 f a i fa_i fai i i i 前面丢失的部分你也给他算了进去,就锅了。

那你考虑怎么搞,想到你已经知道到 f a i fa_i fai 形成的子串最长是 l e n f a i len_{fa_i} lenfai,那简单,直接 m a x n f a i = min ⁡ { m a x n f a i , l e n f a i } maxn_{fa_i}=\min\{maxn_{fa_i},len_{fa_i}\} maxnfai=min{maxnfai,lenfai}

那好,我们现在已经求出了 m a x n i maxn_i maxni,那你考虑怎么搞出所有字符在 i i i 这个位置能匹配的长度 m i n n i minn_i minni
那很明显,其实就是把每次得出的 m a x n i maxn_i maxni 取最小值,就是 m i n n i minn_i minni 了。

那也容易看出,我们只要把所有的 m i n n i minn_i minni 取最大值,就是这道题的答案了。

代码

#include<cstdio>
#include<cstring>
#include<iostream>

using namespace std;

struct node {
	int fa, len;
	int son[31];
	node() {
		fa = len = 0;
		memset(son, 0, sizeof(son));
	}
}d[500001];
char a[250001], b[250001];
int an, bn, tot, lst, ans, maxn[500001], minn[500001];
int tmp[500001], tp[500001];

void SAM_build(int now) {
	int p = lst;
	int np = ++tot;
	lst = np;
	d[np].len = d[p].len + 1;
	
	for (; p && !d[p].son[now]; p = d[p].fa)
		d[p].son[now] = np;
	
	if (!p) d[np].fa = 1;
		else {
			int q = d[p].son[now];
			if (d[q].len == d[p].len + 1) d[np].fa = q;
				else {
					int nq = ++tot;
					d[nq] = d[q];
					d[nq].len = d[p].len + 1;
					d[q].fa = nq;
					d[np].fa = nq;
					for (; p && d[p].son[now] == q; p = d[p].fa)
						d[p].son[now] = nq;
				}
		}
}

void work() {
	int now = 1, nowd = 0;
	memset(maxn, 0, sizeof(maxn));
	for (int i = 1; i <= bn; i++) {
		int go = b[i] - 'a';
		while (now && !d[now].son[go]) now = d[now].fa, nowd = d[now].len;
		if (!now) {
			nowd = 0;
			now = 1;
		}
		else {
			nowd++;
			now = d[now].son[go];
		}
		maxn[now] = max(maxn[now], nowd);
	}
	
	for (int i = tot; i >= 1; i--) {//因为是逆拓扑序,所以要倒序枚举
		now = tp[i];
		maxn[d[now].fa] = max(maxn[d[now].fa], maxn[now]);//向 fa 传递值
		maxn[d[now].fa] = min(maxn[d[now].fa], d[d[now].fa].len);
		//可能要把前面的那一段去掉(因为 fa 已经不包含了),而 fa 最多能有的就是 len[fa],那就直接取 min
		minn[now] = min(minn[now], maxn[now]);//每个字符串的这个都取最小,就是这所有字符串的答案
	}
}

void get_tp() {//基数排序得到逆拓扑序
	for (int i = 1; i <= an; i++)
		tmp[i] = 0;
	for (int i = 1; i <= tot; i++)
		tmp[d[i].len]++;
	for (int i = 1; i <= an; i++)
		tmp[i] += tmp[i - 1];
	for (int i = 1; i <= tot; i++)
		tp[tmp[d[i].len]--] = i;
}

int main() {
//	freopen("read.txt", "r", stdin);
	
	memset(minn, 0x7f, sizeof(minn));
	
	scanf("%s", a + 1);
	an = strlen(a + 1);
	
	tot = 1;
	lst = 1;
	for (int i = 1; i <= an; i++)
		SAM_build(a[i] - 'a');
	
	get_tp();
	
	while (scanf("%s", b + 1) != EOF) {
		bn = strlen(b + 1);
		
		work();
	}
	
	for (int i = 1; i <= tot; i++)
		ans = max(ans, minn[i]);
	
	printf("%d", ans);
	
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值