哈希求最长公共子串
给定 \(n\) 个字符串,长度总和不超过 \(m\),试求这 \(n\) 个字符串的最长公共子串长度。
\(1\le n,m\le 10^5\)
对于这种求最长公共子串的问题,我们可以用哈希解决。
不难发现,最长公共子串具有单调性,这提示我们二分答案。
对于二分的长度 \(len\),考虑如何检验。
首先有一个非常暴力的想法,将所有字符串的长度为 \(len\) 的子串全部取出,如果发现其中一个子串在所有字符串中均出现过,那么检验成功。
具体而言,定义一个二元组 \(\{s,id\}\) 分别表示子串和所处字符串编号,然后进行去重,因为一个字符串可能贡献若干相同二元组,如 \(aaaaa\) 可以贡献 \(2\) 个 \(\{aaaa,1\}\) 的二元组。
去重后,将所有子串相同的二元组放在一起,比如 \(\{aa,1\},\{aa,2\},\{aa,4\}...\) ,如果一个子串相等的连续段中有 \(n\) 个,那么即可说明所有字符串都可以出现该子串,进而说明长度 \(len\) 可行。
截止这里,我们发现整个算法最大的瓶颈就是子串的储存与快速比对,这里将字符串换成对应的哈希值即可。
至于如何将哈希值相等二元组放在一起,直接对二元组进行排序,去重即可。
P5546 [POI2000] 公共串
这就是一道典型的最长公共子串的题目,思路同上,下面给出代码。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
#define PUI pair<ULL,int>
const int N=2100;
int n;
string s[6];
int len[6];
ULL p[N],ha[6][N];
vector<PUI> g;
ULL get(int id,int l,int r) {
return ha[id][r]-ha[id][l-1]*p[r-l+1];
}
int check(int mid) {
g.clear();
for(int i=1;i<=n;i++) {
for(int j=1;j+mid-1<=len[i];j++) {
g.push_back({get(i,j,j+mid-1),i});
}
}
sort(g.begin(),g.end());//排序
g.erase(unique(g.begin(),g.end()),g.end());//去重
int cnt=0; ULL nha=0;
for(int i=0;i<g.size();i++) {
if(cnt==0) {nha=g[i].first; cnt++;}
else {
if(nha==g[i].first) {
cnt++;
if(cnt==n) return 1;
}
else {
cnt=1;
nha=g[i].first;
}
}
}
return 0;
}
void Sol() {
cin>>n;
for(int i=1;i<=n;i++) {
cin>>s[i]; len[i]=s[i].size(); s[i]=" "+s[i];
for(int j=1;j<=len[i];j++) ha[i][j]=ha[i][j-1]*13331+int(s[i][j]-'a');
}
if(n==1) {
cout<<len[1]; return ;
}
int l=0,r=max({len[1],len[2],len[3],len[4],len[5]});
while(l<r) {
int mid=l+r+1>>1;
if(check(mid)) l=mid;
else r=mid-1;
}
cout<<l<<endl;
}
int main(){
p[0]=1;
for(int i=1;i<=2000;i++) p[i]=p[i-1]*13331;
Sol();
return 0;
}