1. 程式人生 > >2217 (最長公共子串問題&&字尾陣列與高度陣列的運用)

2217 (最長公共子串問題&&字尾陣列與高度陣列的運用)

題目連結

題意

給定兩個串a,b。計算兩個字串的最長公共子串的長度。

分析

先考慮簡化問題:
求在一個串中至少出現兩次的最長子串。
答案就是在後綴陣列中相鄰的字尾的最長公共字首。因為在後綴陣列中的起始位置相距越遠,他們的最長公共字首就越小。所以只要求出高度陣列的最大值即可。

問題轉化:
將兩個字串連線起來並在連線處新增一個字元’$’,形成新串s.這樣求在s中至少出現兩次的合適的最長子串就是a、b的最長公共子串。合適的要求是兩個子串的起始位置要一個在a中,一個在b中。

79ms的程式碼


#include <cstdio>
#include <cstring>
#include <algorithm> using namespace std; const int maxn=2e4+100; int n,k,rank[maxn],tmp[maxn],sa[maxn],lcp[maxn]; char a[maxn],b[maxn],s[maxn]; bool cmp_sa(int i,int j)//用倍增法對sa排序 { if(rank[i]!=rank[j]) return rank[i]<rank[j]; else { int ri=i+k<=n?rank[i+k]:-1; int
rj=j+k<=n?rank[j+k]:-1; return ri<rj; } } void get_sa()//求字尾陣列 { for(int i=0;i<=n;i++) { sa[i]=i; rank[i]=i<n?s[i]:-1; } for(k=1;k<=n;k*=2) { sort(sa,sa+n+1,cmp_sa); tmp[sa[0]]=0; for(int i=1;i<=n;i++) { tmp[sa[i]]=tmp[sa[i-1
]]+(cmp_sa(sa[i-1],sa[i])?1:0); } for(int i=0;i<=n;i++) rank[i]=tmp[i]; } } void get_lcp()//求高度陣列 { for(int i=0;i<=n;i++) rank[sa[i]]=i; int h=0; lcp[0]=0; for(int i=0;i<n;i++) { int j=sa[rank[i]-1]; if(h>0) h--; for(;i+h<n && j+h<n;h++) { if(s[i+h]!=s[j+h]) break; } lcp[rank[i]-1]=h; } } int main() { int N; scanf("%d",&N); getchar(); while(N--) { //輸入 gets(a); gets(b); n=0; int len1=strlen(a); int len2=strlen(b); //形成新串s for(int i=0;i<len1;i++) s[n++]=a[i]; s[n++]='$'; for(int i=0;i<len2;i++) s[n++]=b[i]; s[n]='\0'; //計算s的字尾陣列和高度陣列 get_sa(); get_lcp(); //找合適的最大的高度陣列值 int ans=0; for(int i=0;i<n;i++) { if((sa[i]<len1) != (sa[i+1]<len1)) ans=max(ans,lcp[i]); } printf("Nejdelsi spolecny retezec ma delku %d.\n",ans); } return 0; }

模板連結

高度陣列
字尾陣列