poj2217 后缀数组和高度数组经典应用之找出最长公共子串
注意这题不是公共子序列, 子串和子序列的区别在于子序列可以不连续而子串必须连续, 题目连接:http://poj.org/problem?id=2217, 我们可以将两个子串合并到一起, 中间使用$符号隔开, 然后找到最大的lcp即可, 注意对于最大的lcp[i], sa[i], sa[i+1](即最长公共前缀的起点)应该位于$两边。代码如下:
#include <cstdio> #include <cstring> #include <algorithm> #include <string> #include <iostream> using namespace std; const int maxn = 20000 + 100; string s1, s2, s3; int n, k; int rk[maxn], tmp[maxn], sa[maxn], lcp[maxn]; bool cmp_sa(int i, int j){ if(rk[i] != rk[j]) return rk[i] < rk[j]; else { int ri = i+k<=n?rk[i+k]:-1; int rj = j+k<=n?rk[j+k]:-1; return ri < rj; } } void construct_sa(string s, int sa[]){ //处理后缀数组 n = s.length(); for(int i=0; i<=n; i++){ sa[i] = i; rk[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++) rk[i] = tmp[i]; } } void construct_lcp(string s, int sa[], int lcp[]){ //处理高度数组 int n = s.length(); for(int i=0; i<=n; i++) rk[sa[i]] = i; int h = 0; lcp[0] = 0; for(int i=0; i<n; i++){ int j = sa[rk[i]-1]; //获取后缀数组中比i后缀小的那个后缀 if(h > 0) h--; for(; j+h<n&&i+h<n; h++){ if(s[j+h] != s[i+h]) break; } lcp[rk[i]-1] = h; } } char str[10000 + 100]; int main() { int T; scanf("%d", &T); getchar(); while(T--){ gets(str); s1 = str; //cout<<str<<endl; gets(str); s2 = str; //cout<<str<<endl; int len1 = s1.length(); s3 = s1 + '$' + s2; // s3 = s1; construct_sa(s3, sa); //求解s3的后缀数组 // printf("s3.length = %d\n", n); // for(int i=0; i<=n; i++) printf("%d%c", sa[i], i==n?'\n':' '); construct_lcp(s3, sa, lcp); //求解s3的高度数组 // for(int i=0; i<=n; i++) printf("%d%c", lcp[i], i==n?'\n':' '); 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; }