这里主要介绍两种求多个字符串最长公共子串的方法。
这里我们以[POI2000]公共串为例题来讲一下这两种不同的方法的区别。
前置知识:后缀数组、后缀自动机
首先先讲一下SA的求法:
我们可以很容易的想明白,排名越靠近的两个字符串,他们的最长公共前缀,也就是LCP会越大,然后我们考虑将这些字符串连接起来,中间用不同的特殊字符分隔开,去求这个大的字符串的后缀数组。
例如:我们要去求下面三个字符串的最长公共子串
1.abca
2.abc
3.bcde
那么通过我们操作后形成的大字符串就是abca1abc2bcde3
然后我们求出这个大字符串的后缀数组,然后我们来思考一下,如何去计算答案。
很显然,因为经过排序后越相似的子串距离越近,那么我们就可以用类似滑动窗口的那种感觉去选择一段区间,保证选择的这段区间要满足至少包含每一个字符串的一个子串,这样我们求这一段区间的LCP,就代表公共子串的长度,我们求出所有的这些区间,然后将这些长度取maxmaxmax,就可以轻松的得到答案了。
时间复杂度O(字符串总长度)O(字符串总长度)O(字符串总长度)
下面的代码里会有细节的解释

#include<bits/stdc++.h>
#define endl "\n"
#define x first
#define y second
#define Endl endl
typedef long long ll;
const int INF=0x7fffffff;
const double PI=3.14159265359;
using namespace std;
const int N=1e6+10;
string str;
int L[N],R[N],col[N],cnt[N];
int sum=0;
struct SAIS{//这是个SAIS的板子,O(n)求SA  int sa[N],rk[N],s[N<<1],op[N<<1],pos[N<<1],c1[N],c[N];int ht[N];#define re register#define L(x) sa[c[s[x]]--]=x#define R(x) sa[c[s[x]]++]=xinline void sa_sort(int *S,int n,int m,int *s,int *op,int tn){for(re int i=1;i<=n;i++) sa[i]=0;for(re int i=1;i<=m;i++) c1[i]=0;for(re int i=1;i<=n;i++) c1[s[i]]++;for(re int i=2;i<=m;i++) c1[i]+=c1[i-1];for(re int i=1;i<=m;i++) c[i]=c1[i];for(re int i=tn;i;i--) L(S[i]);for(re int i=1;i<=m+1;i++) c[i]=c1[i-1]+1;for(re int i=1;i<=n;i++)if(sa[i]>1 && op[sa[i]-1]) R(sa[i]-1);for(re int i=1;i<=m;i++) c[i]=c1[i];for(re int i=n;i;i--)if(sa[i]>1 && !op[sa[i]-1]) L(sa[i]-1);}void SA_IS(int n,int m,int *s,int *op,int *pos)//m代表字符的范围 {int tot=0,cnt=0;int *S=s+n;op[n]=0;for(re int i=n-1;i;i--) op[i]=(s[i]!=s[i+1])?s[i]>s[i+1]:op[i+1];rk[1]=0;for(re int i=2;i<=n;i++)if(op[i-1]==1 && op[i]==0) pos[++tot]=i,rk[i]=tot;else rk[i]=0;sa_sort(pos,n,m,s,op,tot);int u=0,p=0;for(re int i=1;i<=n;i++)if(rk[sa[i]]){u=rk[sa[i]];if(cnt<=1 || pos[u+1]-pos[u]!=pos[p+1]-pos[p]) ++cnt;else{for(re int j=0;j<=pos[u+1]-pos[u];j++)if(s[pos[u]+j]!=s[pos[p]+j] || op[pos[u]+j]!=op[pos[p]+j]){++cnt;break;}}S[u]=cnt;p=u;}if(tot!=cnt) SA_IS(tot,cnt,S,op+n,pos+n);else for(re int i=1;i<=tot;i++) sa[S[i]]=i;for(re int i=1;i<=tot;i++) S[i]=pos[sa[i]];sa_sort(S,n,m,s,op,tot);}void get_ht(int n){for(re int i=1;i<=n;i++) rk[sa[i]=sa[i+1]]=i;for(re int i=1,p=0;i<=n;ht[rk[i]]=p,i++)if(rk[i]!=1) for(p=p-!!p;sa[rk[i]-1]+p<=n && i+p<=n && s[i+p]==s[sa[rk[i]-1]+p];p++);}void Get_SA(int n){for(int i=1;i<=n;i++) s[i]=str[i];s[++n]=1;SA_IS(n--,122,s,op,pos);//122为字符串的ASCII码范围 get_ht(n);}
}sa;
void add(int x){//类似滑动窗口的感觉,或者说是莫队? if(!col[x]) return;cnt[col[x]]++;if(cnt[col[x]]==1) sum++;
}
void del(int x){if(!col[x]) return;cnt[col[x]]--;if(cnt[col[x]]==0) sum--;
}
void solve(){int ans=0;int n;cin>>n;for(int i=1;i<=n;i++){//L[i]和R[i]表示这个字符串所在的范围 L[i]=R[i-1]+1;string temp;cin>>temp;str+=temp;str+=i+'0';//通过不同的分割符,将这些字符串分隔开 R[i]=L[i]+temp.size();}int len=str.size();str=" "+str;sa.Get_SA(len);for(int i=1;i<=n;i++){for(int j=L[i];j<R[i];j++) col[sa.rk[j]]=i;//将他们的排名染色,便于之后统计区间是否合法 }deque<int>q;//单调队列 求区间最值 int l=1;add(1);for(int r=2;r<=len;r++){while(q.size()&&sa.ht[q.back()]>=sa.ht[r]) q.pop_back();q.push_back(r);add(r);if(sum==n){while(sum==n&&l<r) del(l),l++;l--,add(l);}while(q.size()&&q.front()<=l) q.pop_front();//这里取等于的原因是求LCP的时候 是从l+1到r的ht取min得到的 if(sum==n) ans=max(ans,sa.ht[q.front()]);}cout<<ans<<endl;
}
int main()
{ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);int T;
//  cin>>T;T=1;while(T--){solve();}}



接下来讲一下SAM的做法

如果使用SAM去做这道题的话,我们只需要用其中的一个串建立SAM,然后用其他的串在它上面跑匹配就可以了,匹配时我们可以设置两个变量 ppp(当前状态),lenlenlen(当前匹配长度)。
很明显的一点是,如果当前我们可以匹配上的话,我们就让len++len++len++,否则我们就让ppp跳到fa[p]fa[p]fa[p],并且让lenlenlen更新为跳fafafa后的len[p]en[p]en[p](这里可以直接更新lenlenlen的原因是因为我们已经匹配到了ppp,所以当前状态的所有后缀我们也都匹配到了,所以就可以直接更新了)。
另外我们需要记录一下每一个点被匹配到时最长长度是多少,等全部匹配完之后我们要去更新答案,答案长度是所以字符串匹配到此节点时长度的最小值,等所有的字符串都匹配完后,我们就可以计算答案了。
时间复杂度O(字符串总长度)O(字符串总长度)O(字符串总长度)
下面的代码会给出细节的解释

#include<bits/stdc++.h>
#define endl "\n"
#define x first
#define y second
#define Endl endl
typedef long long ll;
const int INF=0x7fffffff;
const double PI=3.14159265359;
using namespace std;
const int N=1e6+10;
string s[N];
int anslen[N*2],tlen[N*2],col[N*2],cnt[N*2];
int ans=0,n;
struct SAM
{int idx,last,len[N*2],link[N*2],nex[N*2][26];int c[N*2],a[N*2];int sz[N*2];int v[N*2];SAM(){idx=0;last=0;len[0]=0;link[0]=-1;}void insert(int c){int cur=++idx;anslen[cur]=len[cur]=len[last]+1;int p=last;while(p!=-1 && !nex[p][c]) nex[p][c]=cur,p=link[p];if(p==-1) link[cur]=0;else{int q=nex[p][c];if(len[p]+1==len[q]) link[cur]=q;else{int clone=++idx;anslen[clone]=len[clone]=len[p]+1;link[clone]=link[q];for(int i=0;i<26;i++) nex[clone][i]=nex[q][i];while(p!=-1 && nex[p][c]==q) nex[p][c]=clone,p=link[p];link[q]=link[cur]=clone;}}last=cur;}void work(int id,int x){int top=0;int p=0,sum=0;//p代表现在的状态  sum代表现在的匹配长度 for(int i=0;i<s[id].size();i++){while(p!=-1&&nex[p][s[id][i]-'a']==0){p=link[p];if(p==-1) break;sum=len[p];}if(p==-1){p=0;sum=0;}else{sum++;p=nex[p][s[id][i]-'a'];}int k=p;do{v[++top]=k;tlen[k]=max(sum,tlen[k]);//记录这个节点被匹配到时的最长长度 col[k]=x;if(cnt[k]==x-1) cnt[k]=x;//如果该字符串在其它字符串中均出现,让它的出现次数++ k=link[k];}while(k!=0&&col[k]!=x);}for(int j=top;j>=1;j--){int temp=v[j];//答案长度取min anslen[temp]=min(anslen[temp],tlen[temp]);}for(int j=top;j>=1;j--){int temp=v[j];tlen[temp]=0;}}void getans(){for(int i=1;i<=idx;i++){if(cnt[i]==n){ans=max(ans,anslen[i]);}}}
}sam;
void solve(){cin>>n;int minnid=0,minnlen=INF;for(int i=1;i<=n;i++){//将长度最小的字符串建立SAMcin>>s[i];int len=s[i].size();if(minnlen>len){minnlen=len,minnid=i;}}for(int i=0;i<minnlen;i++){sam.insert(s[minnid][i]-'a');}for(int i=1;i<=n;i++){//将所有字符串在SAM上匹配一遍 sam.work(i,i);}sam.getans();cout<<ans<<endl;
}
int main()
{ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);int T;
//  cin>>T;T=1;while(T--){solve();}}

下面说一下两种方法的区别:
首先SA的优点就是方便好写并且容易理解
SAM的优点就是可以求得很大数量级的字符串的公共子串。

最长公共子串(SA和SAM求法)相关推荐

  1. hihocoder 1465 : 后缀自动机五·重复旋律8(后缀自动机+最长公共子串)

    1465 : 后缀自动机五·重复旋律8 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一段音乐旋律可以被表示为一段数构成的 ...

  2. SPOJ - PHRASES Relevant Phrases of Annihilation —— 后缀数组 出现于所有字符串中两次且不重叠的最长公共子串...

    题目链接:https://vjudge.net/problem/SPOJ-PHRASES PHRASES - Relevant Phrases of Annihilation no tags  You ...

  3. 数据结构课程设计---最长公共子串

    数据结构课程设计,由用户输入两个字符串串X和Y,再由用户输入一个任意的字符串Z,实现以下功能: ①如果字符串Z是字符串X的子串,则显示Z在X中的位置并记录,如果字符串Z是字符串Y的子串,则显示Z在Y中 ...

  4. #1415 : 后缀数组三·重复旋律3 (最长公共子串)

    思路 将两个字符串合成一个字符串,中间加上一个"#".然后求height[i]height[i]height[i]. 最长公共子串如果存在,必定是相邻的height[i]heigh ...

  5. SPOJ1812(后缀自动机求n个串的最长公共子串)

    题目:http://www.spoj.com/problems/LCS2/ 题意:给定n个串,求它们的最长公共子串. 思路就是:先将一个串建SAM,然后用后面的串去匹配,对于每一个串,保存最大值,对于 ...

  6. [XSY] 最长公共子串对(后缀自动机)

    最长公共子串对 先放一段考场暴力代码: #include<iostream> #include<cstdio> #include<cstring> using na ...

  7. 2018.12.17-dtoj-1170-最长公共子串

    题目描述: 给定两个字符串A和B,求最长公共子串. 算法标签:后缀数组 以下代码: #include<bits/stdc++.h> #define il inline using name ...

  8. 求两个字符串的LCS(最长公共子串)后缀数组

    题意: 给两个字符串,求出它们的最长公共子串的长度. 比如 yeshowmuchiloveyoumydearmotherreallyicannotbelieveit yeaphowmuchilovey ...

  9. 【动态规划】最长公共子序列与最长公共子串

    1. 问题描述 子串应该比较好理解,至于什么是子序列,这里给出一个例子:有两个母串 cnblogs belong 比如序列bo, bg, lg在母串cnblogs与belong中都出现过并且出现顺序与 ...

最新文章

  1. Windows10远程桌面Ubuntu——把服务器当超高配置的个人电脑使用
  2. 【VMCloud云平台】SCOM配置(四)-监控应用可用性
  3. 海南2021高考成绩分数查询时间,2021海南高考成绩查询公布时间今天几点可以查询...
  4. kadane算法_使用KADANE的算法求最大子阵列和
  5. MySql 优化的 30 条建议
  6. Think in Java之斐波那契数列
  7. js 回调函数 精析
  8. Eclipse配Tomcat服务器
  9. 基于SSM的智慧房屋租赁系统
  10. 网页中透明Flash的设置
  11. 3月24日服务器维护公告,梦幻西游3月24日更新了什么_梦幻西游3月24日维护及系统调整公告_游戏堡...
  12. Java 操作 Office:POI之word图片处理
  13. 【读书笔记】关键影响力:如何调动团队力量
  14. unity 查找所以物体_unity 查找物体的方法(包括隐藏物体)
  15. HDU2066---------Dijkstra算法代码
  16. ps彩色照片变黑白照片
  17. Java基础(一) 八大基本数据类型
  18. Dell(戴尔)电脑恢复BIOS出厂设置的方法
  19. 数据结构与算法A实验六图论---7-3 旅游规划(Dijkstra算法)
  20. 盘一盘 Python - PyEcharts (v1.0)

热门文章

  1. matlab求偏微分方法解析解,偏微分数值解(2,MATLAB求解方法)学案.ppt
  2. 3D场景上的人体生成器【附有源码】
  3. 【搞搞算法】多目标遗传算法NSGA-II的C语言代码使用手册
  4. ​Python 3.7.0 程序下载 32/64位含安装教程|支持Win10
  5. 矩阵和图结构(图论) 最短路径问题 学习笔记
  6. 1到n中能被3和7整除的数的和
  7. 计算机大学规划书800字,2022年大学计划书范文800字3篇
  8. 怎样更换计算机开机画面,如何给win10电脑更换一个漂亮的开机界面
  9. NPC问题的证明(可满足性问题、3-CNF可满足性问题、团问题、顶点覆盖问题)
  10. pdf java解析_JAVA解析PDF内容