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;
}