POJ - 2774-Long Long Message-( 后缀数组 / 后缀自动机 / 二分 + 哈希 )

题目链接:点击进入
题目

在这里插入图片描述
在这里插入图片描述

题意

求两个字符串最长连续公共子串的长度。

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值