题目链接:点击进入
题目


题意
求两个字符串最长连续公共子串的长度。
思路1:后缀数组
定义以 s 的第 i 个字符为第一个元素的后缀为 suff ( i ) ;
定义 LCP ( i , j ) 为 suff ( sa [ i ] ) 与 suff ( sa [ j ] ) 的最长公共前缀 ;
LCP的几条性质:
1、LCP ( i , j ) = LCP ( j , i ) ;
2、LCP ( i , i ) = len ( sa [ i ] ) = n - sa [ i ] + 1 ;
2、LCP ( i , k ) = min ( LCP ( i , j ) , LCP ( j , k ) ) 对于任意 1<= i <= j <= k <= n ;
4、LCP ( i , k ) = min ( LCP ( j , j - 1 ) ) 对于任意 1 < i <= j <= k <= n ;
5、LCP ( i , k ) = min ( height [ j ] ) i + 1 <= j <= k ;
height [ i ] 为 LCP ( i , i - 1 ) ,1 < i <= n ( height [ 1 ] = 0 )
字符串的任何一个子串都是这个字符串的某个后缀的前缀。求字符串 S 和 T 的最长连续公共子串相当于求 S 的后缀和 T 的后缀的最长公共前缀的最大值。如果暴力枚举 S 和 T 的全部后缀求最大LCP,那么效率会很低。可以将 S 和 T 合成一个字符串 A ( 中间用一个没有出现过的字符隔开,方便排序后求最长连续公共子串 ),然后求 A 的后缀数组,求出 height 数组,同时不是所有的 height 值都是符合要求的,要保证 suffix ( sa [ i - 1 ] ) 和 suffix ( sa [ i ] ) 分别是 S 和 T 两个字符串中的后缀,这样的 height 值才可以更新答案。
代码1
#include<iostream>
#include<string>
#include<map>
#include<set>
//#include<unordered_map>
#include<queue>
#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
#include<iomanip>
#include<cmath>
#include<fstream>
#define X first
#define Y second
#define best 131
#define INF 0x3f3f3f3f3f3f3f3f
#define pii pair<int,int>
#define lowbit(x) x & -x
#define inf 0x3f3f3f3f
//#define int long long
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double eps=1e-7;
const double pai=acos(-1.0);
const int N=2e4+10;
const int maxn=1e6+10;
const int mod=1e9+7;
int y[maxn],x[maxn],c[maxn],sa[maxn],rk[maxn],height[maxn],dmin[maxn][20];;
int n,m;
char s[maxn],t[maxn];
void get_sa()
{
for(int i=1;i<=n;++i) ++c[x[i]=s[i]];
for(int i=2;i<=m;++i) c[i]+=c[i-1];
for(int i=n;i>=1;--i) sa[c[x[i]]--]=i;
for(int k=1;k<=n;k<<=1)
{
int num=0;
for(int i=n-k+1;i<=n;++i) y[++num]=i;
for(int i=1;i<=n;++i) if(sa[i]>k) y[++num]=sa[i]-k;
for(int i=1 ;i<=m;++i) c[i]=0;
for(int i=1;i<=n;++i) ++c[x[i]];
for(int i=2;i<=m;++i) c[i]+=c[i-1];
for(int i=n;i>=1;--i) sa[c[x[y[i]]]--]=y[i],y[i]=0;
swap(x,y);x[sa[1]]=1;num=1;
for(int i=2;i<=n;++i)
x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num;
if(num==n) break;
m=num;
}
}
void get_height()
{
int k=0;
for(int i=1;i<=n;++i) rk[sa[i]]=i;
for(int i=1;i<=n;++i)
{
if(rk[i]==1) continue;
if(k) k--;
int j=sa[rk[i]-1];
while(j+k<=n&&i+k<=n&&s[i+k]==s[j+k]) k++;
height[rk[i]]=k;
}
}
void initMin()
{
for(int i=1;i<=n;i++) dmin[i][0]=height[i];
for(int j=1;(1<<j)<=n;j++)
for(int i=1;i+(1<<j)-1<=n;i++)
dmin[i][j]=min(dmin[i][j-1] , dmin[i+(1<<(j-1))][j-1]);
}
int RMQ(int L,int R)//取得范围最小值
{
int k=0;
while((1<<(k+1))<=R-L+1)k++;
return min(dmin[L][k] ,dmin[R-(1<<k)+1][k]);
}
int LCP(int i,int j)//求后缀i和j的LCP最长公共前缀
{
int L=rk[i],R=rk[j];
if(L>R) swap(L,R);
L++;
return RMQ(L,R);
}
int main()
{
cin>>s+1>>t+1;
int l1=strlen(s+1);
int l2=strlen(t+1);
s[l1+1]=125;
for(int i=1;i<=l2;i++)
s[l1+i+1]=t[i];
n=l1+l2+1;m=140;
get_sa();
get_height();
int maxx=0;
for(int i=2;i<=n;i++)
{
if(height[i]>maxx)
{
int a=sa[i-1];
int b=sa[i];
if(a>0&&a<=l1+1&&b>l1+1)
if(height[i]>maxx)
maxx=height[i];
if(b>0&&b<=l1+1&&a>l1+1)
if(height[i]>maxx)
maxx=height[i];
}
}
cout<<maxx<<endl;
return 0;
}
思路2:二分+哈希
最长公共子串存在单调性: 如果两个串存在长度为 k 的公共子串,那么必然存在长度 0 到 k -1 的公共子串。
根据单调性,二分求最长公共子串长度,然后 O ( n ) 算出两个串长度为 len 的子串的哈希值,然后排序+二分查找是否存在相同的哈希值( 即公共子串 )
代码2
#include<iostream>
#include<string>
#include<map>
#include<set>
//#include<unordered_map>
#include<queue>
#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
#include<iomanip>
#include<cmath>
#include<fstream>
#define X first
#define Y second
#define base 131
#define INF 0x3f3f3f3f3f3f3f3f
#define pii pair<int,int>
#define lowbit(x) x & -x
#define inf 0x3f3f3f3f
//#define int long long
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double eps=1e-7;
const double pai=acos(-1.0);
const int N=2e4+10;
const int maxn=1e6+10;
const int mod=1e9+7;
int n,m,k,cnt,tot,l1,l2;
ull h1[maxn],h2[maxn],p[maxn],ans,h[maxn];
char s[maxn],t[maxn];
void hash_init()
{
p[0]=1;h1[0]=0;h2[0]=0;
for(int i=1;i<=max(l1,l2);i++)
p[i]=p[i-1]*base;
for(int i=1;i<=l1;i++)
h1[i]=h1[i-1]*base+s[i];
for(int i=1;i<=l2;i++)
h2[i]=h2[i-1]*base+t[i];
}
ull hash_code(int l, int r,int op)
{
if(op==1)
return h1[r]-h1[l-1]*p[r-l+1];
else
return h2[r]-h2[l-1]*p[r-l+1];
}
bool check(int len) //检查长度为len的公共子串是否存在
{
for(int i=len;i<=l1;i++)
h[i-len]=hash_code(i-len+1,i,1);//算出字符串 s 中所有区间[i,i+len-1]的子串哈希值,保存并排序
sort(h,h+l1-len+1);
for(int i=len;i<=l2;i++)
if(binary_search(h,h+l1-len+1,hash_code(i-len+1,i,2))) //算出字符串 t 中所有区间[i,i+len-1]的子串哈希值
return 1; //并在数组 h 中二分查找是否有相同的值,若有则存在长度为len的公共子串
return 0;
}
int main()
{
cin>>s+1>>t+1;
l1=strlen(s+1),l2=strlen(t+1);
hash_init();
int l=1,r=min(l1,l2);
while(l<=r)//二分求最长公共子串长度
{
int mid=l+r>>1;
if(check(mid))
{
ans=mid;
l=mid+1;
}
else r=mid-1;
}
cout<<ans<<endl;
return 0;
}
思路3:后缀自动机
( 记录模板 )
代码3
#include<iostream>
#include<string>
#include<map>
#include<set>
//#include<unordered_map>
#include<queue>
#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
#include<iomanip>
#include<cmath>
#include<fstream>
#define X first
#define Y second
#define base 131
#define INF 0x3f3f3f3f3f3f3f3f
#define pii pair<int,int>
#define lowbit(x) x & -x
#define inf 0x3f3f3f3f
//#define int long long
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double eps=1e-7;
const double pai=acos(-1.0);
const int N=2e4+10;
const int maxn=1e6+10;
const int mod=1e9+7;
int par[maxn<<1],son[maxn<<1][30];
int deep[maxn<<1],cnt,root,last,x,l1,l2,sum,ans;
char s[maxn],t[maxn];
void SAM(int ch)
{
int u=last;
deep[++cnt]=deep[last]+1,last=cnt;
while(u&&!son[u][ch]) son[u][ch]=last,u=par[u];
if(!u) par[last]=root;
else
{
int v=son[u][ch];
if(deep[v]==deep[u]+1) par[last]=v;
else
{
deep[++cnt]=deep[u]+1;
int next=cnt;
memcpy(son[next],son[v],sizeof(son[v]));
par[next]=par[v],par[v]=par[last]=next;
while(u&&son[u][ch]==v) son[u][ch]=next,u=par[u];
}
}
}
int main()
{
cin>>s+1>>t+1;
l1=strlen(s+1),l2=strlen(t+1);
deep[++cnt]=0;x=root=last=cnt;
for(int i=1;i<=l1;i++) SAM(s[i]-'a');
for(int i=1;i<=l2;i++)
{
if(son[x][t[i]-'a']) x=son[x][t[i]-'a'],sum++;
else
{
while(x&&son[x][t[i]-'a']==0) x=par[x];
if(!x) x=root,sum=0;
else sum=deep[x]+1,x=son[x][t[i]-'a'];
}
ans=max(ans,sum);
}
cout<<ans<<endl;
return 0;
}
最长公共子串算法

456





