题意:给定两个字符串,求这两个字符串的最长连续子串(注意和最长公共子串LCS的区别。LCS用dp来求)。
思路:后缀数组。有关后缀数组的概念可以参见罗穗骞的一篇文章。这道题的思路就是将两个字符串连接起来(中间用一个不相关的字符分开),再用后缀数组求出height数组的值,找出一个height值最大并且i与i-1的sa值分别在两串字符中就OK。
其中一个地方wa了n次,在最后判断的时候抖了个机灵写成(sa[i]-len1)*(sa[i-1]-len1)<0,原本以为其和(sa[i]<len1&&sa[i-1]>len1)||(sa[i]>len1&&sa[i-1]<len1)表达相同的意思。wa的原因应该和数据相乘溢出有关,因为字符串长度上界是200000,如果两个乘数都较大(比如都为50000),那么相乘溢出了int范围为负数,导致错误。
#include <stdio.h>
#include <string.h>
#define swap(a,b,c) c = a,a = b,b = c;
#define N 200005
char s[N];
int top[N],rank[N],b[N],v[N],r[N],sa[N],rank[N],h[N];
int cmp(int *q,int x,int y,int j){//非常巧妙。如果r[a]=r[b],说明以r[a]或r[b]开头的长度为l的字符串肯定不包括字符r[n-1],所以调用变量r[a+l]和r[b+l]不会导致数组下标越界
return (q[x]==q[y])&&(q[x+j]==q[y+j]);//表示当前比较的2j长的字符串中前j个相同,后j个也相同
}
void getsa(int n,int m){//m是计数排序中的字符个数
int i,j,p;
int *x = rank,*y = b,*t;
memset(top,0,sizeof(top));
for(i = 0;i<n;i++)
top[ x[i]=r[i] ]++;
for(i = 1;i<m;i++)
top[i] += top[i-1];
for(i = n-1;i>=0;i--)//对长度为1的字符串进行排序(结果相当于对原字符串进行排序,此时sa保存的是排名为下标的是第几个字符)
sa[--top[x[i]]] = i;
for(p = j = 1;p<n;j*=2,m=p){
for(p = 0,i = n-j;i<n;i++)
y[p++] = i;
for(i = 0;i<n;i++)
if(sa[i] >= j)
y[p++] = sa[i]-j;
for(i = 0;i<n;i++)
v[i] = x[y[i]];
memset(top, 0, sizeof(top));
for(i = 0;i<n;i++)
top[v[i]]++;
for(i = 1;i<m;i++)
top[i] += top[i-1];
for(i = n-1;i>=0;i--)
sa[--top[v[i]]] = y[i];
swap(x,y,t);
x[sa[0]] = 0;
for(p = i = 1;i<n;i++)
x[sa[i]] = cmp(y,sa[i-1],sa[i],j) ? p-1:p++;
}
}
void getlcp(int n){//longest common prefix
int i,j,k=0;
for(i = 1;i<=n;i++)
rank[sa[i]] = i;
for(i = 0;i<n;i++){
if(k)
k--;
j = sa[rank[i]-1];
while(r[i+k] == r[j+k])
k++;
h[rank[i]] = k;
}
}
int main(){
while(scanf("%s",s) != EOF){
int i,len1,len,res=0;
len1 = (int)strlen(s);
s[len1] = 'a'+26;
scanf("%s",s+len1+1);
len = (int)strlen(s);
for(i = 0;i<len;i++)
r[i] = s[i] - 'a' + 1;
r[len] = 0;
getsa(len+1,29);
getlcp(len);
for(i = 1;i<=len;i++)
if(h[i]>res && ((sa[i]<len1&&sa[i-1]>len1)||(sa[i]>len1&&sa[i-1]<len1)))
res = h[i];
/*if(h[i]>res && ((sa[i]-len1)*(sa[i-1]-len1)<0))
res = h[i];*/
printf("%d\n",res);
}
return 0;
}