POJ-1743-Musical Theme(后缀数组+二分)

本文介绍一种高效算法来寻找字符串中最长的非重叠重复子串。通过使用后缀数组和高度数组,结合二分查找技巧,实现O(nlogn)的时间复杂度。文章还提供了一个具体的代码实现案例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >



Sample Input

30
25 27 30 34 39 45 52 60 69 79 69 60 52 45 39 34 30 26 22 18
82 78 74 70 66 67 64 60 65 80
0

Sample Output

5


大致题意:求不可重叠最长重复子串  

参考 国家集训队论文:算法合集之《后缀数组——处理字符串的有力工具》

 先二分答案,把题目变成判定性问题:
判断是否存在两个长度为k的子串是相同的,且不重叠。
解决这个问题的关键还是利用height数组。
把排序后的后缀分成若干组,其中每组的后缀之间的height值都不小于k。
例如,字符串为“aabaaaab”,当k=2时,后缀分成了4组 






 容易看出,有希望成为最长公共前缀不小于k的两个后缀一定在同一组。

然后对于每组后缀,只须判断每个后缀的sa值的最大值和最小值之差是否不小于k。

如果有一组满足,则说明存在,否则不存在。整个做法的时间复杂度为O(nlogn)。

本题中利用height值对后缀进行分组的方法很常用,请读者认真体会。



需要注意的地方,字符串预处理:令 a[i]=a[i+1]-a[i]+size,与a[i]=a[i]-a[i-1]+size 相比,一个掐头,一个去尾,显然去尾是比较理智的;另外最后结果要+1.

由于最长匹配为n/2,合法长度需>4,所以可从[4,n/2]区间开始二分。


CODE:

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stack>
#include <queue>
#include <map>
#include <set>
#include <vector>
#include <math.h>
#include <bitset>
#include <algorithm>
#include <climits>
using namespace std;
#define MAXN 200010
int t1[MAXN],t2[MAXN],c[MAXN];
int sa[MAXN],rank[MAXN],height[MAXN];
int r[MAXN];
char s1[MAXN],s2[MAXN];
int s[MAXN];
bool cmp(int *r, int a, int b,int l)
{
    return r[a]==r[b]&&r[a+l]==r[b+l];
}
void DA(int str[], int sa[], int rank[], int height[], int n, int m)
{
   // n++;
    int i,j,p,*x=t1,*y=t2;
    for(i=0; i<m; ++i)c[i]=0;
    for(i=0; i<n; ++i)c[x[i]=str[i]]++;
    for(i=1; i<m; ++i)c[i]+=c[i-1];
    for(i=n-1; i>=0; --i)sa[ --c[ x[i] ] ]=i;
    for(j=1; j<=n; j<<=1)
    {
        p=0;
        for(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<m; ++i)c[i]=0;
        for(i=0; i<n; ++i)c[x[y[i]]]++;
        for(i=1; i<m; ++i)c[i]+=c[i-1];
        for(i=n-1; i>=0; --i)sa[--c[x[y[i]]]]=y[i];
        swap(x,y);
        p=1;
        x[sa[0]]=0;
        for(i=1; i<n; ++i)
            x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
        //if(p>=n)break;
        m=p;
    }
    int k=0;
    n--;
    for(i=0; i<=n; ++i)rank[sa[i]]=i;
    for(i=0; i<n; ++i)
    {
        if(k)--k;
        j=sa[rank[i]-1];
        while(str[i+k]==str[j+k])k++;
        height[rank[i]]=k;
    }
}
bool find_k(int k,int n)
{
    int a,b,i;
    a=b=sa[1];
    for(i=2; i<=n; ++i)
    {
        if(height[i]<k)
            a=b=sa[i];
        else
        {
            a=min(a,sa[i]);
            b=max(b,sa[i]);
            if(b-a>=k)return 1;
        }
    }
    return 0;
}
int a[20005];
int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        if(!n)break;
        int k,j=0;
         for(int i=0; i<n; ++i)
            scanf("%d",&a[i]);
        for(int i=0; i<n-1; ++i)
            a[i]=a[i+1]-a[i]+100;
        a[n-1]=0;
        DA(a,sa,rank,height,n,200);
        int a=4,b=n>>1,mid;
        int ans=0;
        while(a<=b)
        {
            mid=(a+b)>>1;
            if(find_k(mid,n))
            {
                ans=mid;
                a=mid+1;
            }
            else b=mid-1;
        }
        if(ans>=4)printf("%d\n",ans+1);
        else printf("0\n");
    }
    return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值