题意:给定两个字符串,求这两个字符串的最长连续子串(注意和最长公共子串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; }