题意:求n个字符串的最长公共子串
思路:后缀数组+二分
1.若是求两个字符串的最长公共子串的长度且两字符串不太长本可以用dp
2. 因为后缀数组方便处理子串问题,所以可以将问题转化成特殊的子串问题。所以将若干字符串拼成一个字符串s,并记录这个长串的每一部分属于哪个短的字符串。
3. 但是这跟求子串的最长重叠长度不同,子串是“分组”的。
4. 所求的不是具体的串而是长度,且这个公共串有单调性,即公共串越短越能满足条件,越长越不能满足条件。所以考虑二分答案。
5. 如何check?若存在最长的公共子串,那么他们一定连续地存在于s按字典序排成的后缀中。所以只要检验是否存在一段连续的height值大于now,且他们存在于n个不同的组中即可。
6. 连接的时候用的字符一定不能是一样的,否则他们连续地存在于按字典序排列的后缀中,会造成答案错误。我就这样wa了一次
#include<bits/stdc++.h>
#define FAST ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define INF 0x3f3f3f3f
typedef long long ll;
const int maxn = 1e5+5;
using namespace std;
int t;
int n,m;
char s[maxn],str[maxn];
int pos[maxn],vis[10];
int sa[maxn],rk1[maxn],tp1[maxn],tax[maxn],height[maxn];
int *rk=rk1, *tp=tp1;
void bucket()
{
for (int i=0; i<=m; i++) tax[i]=0;
for (int i=1; i<=n; i++) tax[rk[i]]++;
for (int i=1; i<=m; i++) tax[i]+=tax[i-1];
for (int i=n; i>=1; i--) sa[tax[rk[tp[i]]]--]=tp[i];
}
void get_sa()
{
m=128;
for (int i=1; i<=n; i++) rk[i]=s[i],tp[i]=i;
bucket();
for (int k=1,p=0; p<n; m=p,k<<=1)
{
p=0;
for (int i=1; i<=k; i++) tp[++p]=n-k+i;
for (int i=1; i<=n; i++)
if (sa[i]>k) tp[++p]=sa[i]-k;
bucket();
swap(tp,rk);
rk[sa[1]]=p=1;
for (int i=2; i<=n; i++)
rk[sa[i]]=(tp[sa[i-1]]==tp[sa[i]] && tp[sa[i-1]+k]==tp[sa[i]+k])?p:++p;
}
}
void get_height()
{
int k=0;
for (int i=1; i<=n; i++)
{
if (k) k--;
int j=sa[rk[i]-1];
while(s[i+k]==s[j+k]) k++;
height[rk[i]]=k;
}
}
bool check(int now)
{
for (int i=1; i<=n; i++)
{
if (height[i]<now) memset(vis,0,sizeof(vis));
//[1...i-1]那些后缀不会是满足条件的一段
vis[pos[sa[i]]]++;
//但是这个后缀有可能成为新的一段的开始
int flag=1; //下面几行是检验有没有找到一段符合要求
for (int j=1; j<=t; j++)
if (!vis[j]) { flag=0; break; }
if (flag==1) return true;
}
return false;
}
int main()
{
FAST;
cin>>t;
for (int i=1; i<=t; i++)
{
cin>>str+1;
for (int j=1; str[j]; j++) s[++n]=str[j],pos[n]=i;
s[++n]='z'+i; //连接处不能一样
}
get_sa();
get_height();
int l=0,r=n+1;
int ans=-1;
while(l<=r)
{
int mid=(l+r)/2;
if (check(mid)) l=mid+1, ans=mid;
else r=mid-1;
}
cout<<ans<<endl;
return 0;
}
我觉得check比较难写,这个check的算法可以拓展为一种检验是否存在一段连续区间满足某种性质的算法。