2811. 最长公共子串
题目链接
2811. 最长公共子串
给定 \(n\) 个字符串,试求出这些字符串的最长公共子串。
输入格式
第一行一个整数 \(n\)。
下面第 \(2\) 到 \(n+1\) 行,每行一个字符串。
输出格式
仅一行,包含一个正整数,表示 \(n\) 个字符串的最长公共子串长度。
数据范围
\(2≤n≤11,\)
每个字符串的长度不超过 \(10000\),
字符串均由小写字母构成。
输入样例:
2
ababc
cbaab
输出样例:
2
解题思路
后缀自动机
先考虑暴力求解两个字符串的最长公共子串的情况:假设已经将一个字符串的所有子串求出,当前枚举另外一个字符串的开始位置,假设枚举到某个位置时停下来,此时开始位置应该后移一位继续匹配,\(\color{red}{如何将此操作转移到后缀自动机上并且优化过程?}\)
等价于先建立一个字符串的后缀自动机,然后在后缀自动机的有向无环图上匹配另外一个,如果走得动,则更新到达该节点的最长路径,否则,本来是要到减少该路径上的第一个字符表示的节点上继续匹配的,但是如果该字符串不是该节点表示的最短子串的话,后面还是会走到该节点,而且此时路径还要比之前短,所以当初应该直接跳向最短串减去第一个字符表示的节点上,即其父节点上,但此时该父节点可能没有更新,由于该父节点的最长子串为其子节点的后缀,子节点中存在某一个子串的话说明父节点表示的所有串都存在,此时父节点应该更新为该节点表示的最长串长度,即需要向上传递更新父节点为该节点的最长子串长度,但这样每次走不动就更新的话复杂度过大,可以先不更新,处理完匹配串后,建立后缀自动机表示的树,由下向上传递最大值,\(\color{red}{为什么是下面节点的最大值,当前节点表示的最长子串的最大值显然都要小于子节点中表示的子串的长度?}\)对于每一个节点而言,由于要取的是公共子串,其他所有子串走到该节点可能会形成长度不同的子串,此时对于该节点应该取最小的子串这样才能使所有字符串都含有该子串,所以答案节点可以初始化为节点表示的子串的最长长度,然后当向上传递标记时传递最大值最后更新答案时对答案就不会有影响
- 时间复杂度:\(O(\sum_{i=1}^n len(s[i]))\)
代码
// Problem: 最长公共子串
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/2813/
// Memory Limit: 64 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
// %%%Skyqwq
#include <bits/stdc++.h>
//#define int long long
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
template <typename T> void inline read(T &x) {
int f = 1; x = 0; char s = getchar();
while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
x *= f;
}
const int N=20005;
int n,res[N],now[N],cnt=1,lst=1;
int e[N],ne[N],h[N],idx=1;
char s[N];
struct Node
{
int fa,len;
int ch[26];
}node[N];
void extend(int c)
{
int p=lst,np=lst=++cnt;
node[np].len=node[p].len+1;
for(;p&&!node[p].ch[c];p=node[p].fa)node[p].ch[c]=np;
if(!p)node[np].fa=1;
else
{
int q=node[p].ch[c];
if(node[q].len==node[p].len+1)node[np].fa=q;
else
{
int nq=++cnt;
node[nq]=node[q];
node[nq].len=node[p].len+1;
node[q].fa=node[np].fa=nq;
for(;p&&node[p].ch[c]==q;p=node[p].fa)node[p].ch[c]=nq;
}
}
}
void add(int a,int b)
{
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int x)
{
for(int i=h[x];~i;i=ne[i])
{
int y=e[i];
dfs(y);
now[x]=max(now[x],now[y]);
}
}
int main()
{
scanf("%d",&n);
scanf("%s",s);
for(int i=0;s[i];i++)extend(s[i]-'a');
memset(h,-1,sizeof h);
for(int i=2;i<=cnt;i++)add(node[i].fa,i);
for(int i=1;i<=cnt;i++)res[i]=node[i].len;
while(--n)
{
scanf("%s",s);
int t=0,p=1;
memset(now,0,sizeof now);
for(int i=0;s[i];i++)
{
int c=s[i]-'a';
while(p>1&&!node[p].ch[c])p=node[p].fa,t=node[p].len;
if(node[p].ch[c])t++,p=node[p].ch[c];
now[p]=max(now[p],t);
}
dfs(1);
for(int j=1;j<=cnt;j++)res[j]=min(res[j],now[j]);
}
int ans=0;
for(int i=1;i<=cnt;i++)ans=max(ans,res[i]);
printf("%d",ans);
return 0;
}