【后缀数组求不可重叠最长重复子串】POJ 1743

来源:互联网 发布:万夫莫开贾克斯cdk淘宝 编辑:程序博客网 时间:2024/06/05 09:18
不可重叠最长重复子串(pku1743)
给定一个字符串,求最长重复子串,这两个子串不能重叠。
算法分析:
这题比上一题稍复杂一点。先二分答案,把题目变成判定性问题:判断是否存在两个长度为k 的子串是相同的,且不重叠。解决这个问题的关键还是利用height 数组。把排序后的后缀分成若干组,其中每组的后缀之间的height 值都不小于k。例如,字符串为“aabaaaab”,当k=2 时,后缀分成了4 组,如图5所示。


容易看出,有希望成为最长公共前缀不小于k 的两个后缀一定在同一组。然后对于每组后缀,只须判断每个后缀的sa 值的最大值和最小值之差是否不小于k。如果有一组满足,则说明存在,否则不存在。整个做法的时间复杂度为O(nlogn)。本题中利用height 值对后缀进行分组的方法很常用,请读者认真体会。

#define maxn 20010int wa[maxn],wb[maxn],wv[maxn],wss[maxn];int r[maxn],sa[maxn];int cmp(int *r,int a,int b,int l){return r[a]==r[b] && r[a+l]==r[b+l];}/*【倍增算法O(nlgn)】待排序的字符串放在r 数组中,从r[0]到r[n-1],长度为n,且最大值小于m  使用倍增算法前,需要保证r数组的值均大于0。然后要在原字符串后添加一个0号字符  所以,若原串的长度为n,则实际要进行后缀数组构建的r数组的长度应该为n+1.所以调用da函数时,对应的n应为n+1.*/void da(int *r,int *sa,int n,int m){//n要加1     int i,j,p,*x=wa,*y=wb,*t;     for(i=0;i<m;i++) wss[i]=0;     for(i=0;i<n;i++) wss[x[i]=r[i]]++;     for(i=1;i<m;i++) wss[i]+=wss[i-1];     for(i=n-1;i>=0;i--) sa[--wss[x[i]]]=i;     for(j=1,p=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++) wv[i]=x[y[i]];         for(i=0;i<m;i++) wss[i]=0;         for(i=0;i<n;i++) wss[wv[i]]++;         for(i=1;i<m;i++) wss[i]+=wss[i-1];         for(i=n-1;i>=0;i--) sa[--wss[wv[i]]]=y[i];         for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;i++)         x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;     }     return;}int rank[maxn],height[maxn];//rank[i]:i排第几;sa[i]:排第i的后缀串在哪里,互为逆运算void calheight(int *r,int *sa,int n){//n不用加1     int i,j,k=0;     for(i=1;i<=n;i++) rank[sa[i]]=i;     for(i=0;i<n;height[rank[i++]]=k){        for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);     }     return;}int main(){    int n;    while(scanf("%d",&n) && n){        int i;        int x,y;        scanf("%d",&x);        for(i=0;i<n-1;i++){            scanf("%d",&y);            r[i] = y - x + 100;//不能出现负数            x = y;        }cout<<endl;        r[n-1] = 0;//总共有n-1个值        da(r,sa,n,190);        calheight(r,sa,n-1);        for(i=1;i<=n-1;i++)        cout<<height[i]<<endl;        int l = 0,r = n-1;        int mid;        int ans=0;        while(l<=r){            mid = (l+r)>>1;            int minm = sa[1];            int maxm = sa[1];            bool ok = 0;            for(i=2;i<=n;i++){//不写i<=n-1的原因系这样可以处理完最后一组                if(height[i]>=mid && i!=n){                    if(sa[i]>maxm)maxm = sa[i];                    if(sa[i]<minm)minm = sa[i];                } else {                    if(maxm - minm>=mid){                        ok = 1;                        break;                    } else minm = maxm = sa[i];                }            }            if(ok){                ans = mid;                l = mid+1;            } else r = mid-1;        }        //ans+1的原因是前面的处理是把后一个减去前一个的值,所以最后要+1个        if(ans+1<=4)printf("%d\n",0);        else printf("%d\n",ans+1);    }    return 0;}


















原创粉丝点击