poj 2774 后缀数组(最长连续子串)

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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值