poj1743 Musical Theme(后缀数组)

题目链接:poj1743

题目大意; 

给你一个长为n的数字序列,要你求出该序列中最长的满足下面要求的序列长度:

两个序列长度大于等于5

原序列中存在不重叠的两个序列,

这两个序列长度相同且将第一个序列的所有数字加上某个常数可以得到第二个序列.

 本题可见罗穗骞《后缀数组——处理字符串的有力工具》.点击打开链接

        首先我们要求的是长度至少为5的这种不重叠序列,其实要求的这两个序列相似等价于他们两个序列中相邻元素的增幅完全一样.所以我们可以用长n的原数组的后一个数减前一个数得到长为n-1的一个新数组.

        然后在这个新数组中我们只要找最长的不重叠的重复出现2次的最长连续字串即可.

        上面这个问题就可以用后缀数组来解了.我们将上面的问题变成判定问题:是否存在一个长度为k的不重叠子序列?

        首先对新的数组求sa和height,然后将n(新数组n-1个数,但是需要加上尾0,变成n个数)个排序后的后缀分成几组,保存每组内的height值>=k.那么如果有长为k的重复不重叠子序列,那么它一定是同一组后缀的两个前缀了.如果有一组的sa最大值与sa最小值之差>k,那么就存在这样的序列,否则k就不合适.

接下来有一个点需要注意;题目数据不强,网上一些代码很多都是不正确的。

  • 首先把问题转化成重复子串的问题:把原串每一位都与前一位相减。这样得出的新串如果有两个长度为n的子串相同,那么它们对应在原串的长度n+1的子串也就相似。
  • 所以接下来要求的就是这个新串不可“重叠”最长重复子串——问题就在这儿,这不只是要求不可重叠,还要求两个子串要隔至少一个位置,因为如果两个子串靠在一起这样反应到原串那两个子串各自的首尾是重合的。

比如数据:9  1 1 1 1 1 1 1 1 1

隔至少一个位置其实只要原本的if(mx-mm>=k)改成if(mx-mm>k)就行了。

 

最后大概描述一下不可重叠最长重复子串的解法:

  • O(logn)二分枚举子串长度,判断解是否成立
  • O(n)判断长度是否成立:把互相之间LCP大于等于长度的分为一组,这通过个扫一遍height即可,因为后缀是有序的,相邻的后缀间的LCP必定的极大的;接下来就找到每个组里后缀sa值最大和最小的,如果差值大于(等于)k就成立,因为这样小下标的后缀沿着LCP下去走k步才不会盖到大下标的后缀。

 接下里提供一个样例来测试;

11
1 2 3 4 5 6 7 8 9 10 11

正确答案是5  网上有的代码答案是6,竟然也能ac

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=20000+100;
const int maxm=20000+100;
struct SuffixArray
{
    int s[maxn];
    int sa[maxn],rank[maxn],height[maxn];
    int t1[maxn],t2[maxn],c[maxm],n;
    void build_sa(int m)
    {
        int i,*x=t1,*y=t2;
        for(i=0;i<m;i++) c[i]=0;
        for(i=0;i<n;i++) c[x[i]=s[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(int k=1;k<=n;k<<=1)
        {
            int p=0;
            for(i=n-k;i<n;i++) y[p++]=i;
            for(i=0;i<n;i++)if(sa[i]>=k) y[p++]=sa[i]-k;
            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]]= y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k]? p-1:p++;
            if(p>=n) break;
            m=p;
        }
    }
    void build_height()
    {
        int i,j,k=0;
        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(s[i+k]==s[j+k]) k++;
            height[rank[i]]=k;
        }
    }
    bool check(int k)//长l的出现k次以上的串
    {
        int maxe,mine;
        maxe=mine=sa[1];
        for(int i=2;i<n;i++)
        {
            if(height[i]<k)
                maxe=mine=sa[i];
            else
            {
                maxe=max(maxe,sa[i]);
                mine=min(mine,sa[i]);
                if(maxe-mine>k) return true;
                //中间至少空一个  防止两个序列首尾相连
            }
        }
        return false;
    }
}sa;

int main()
{
    int n;
    int m,k;
    while(scanf("%d",&n)==1&&n)
    {
    	scanf("%d",&m);
        for(int i=0;i<n-1;i++)
        {
            scanf("%d",&k);
            sa.s[i]=k-m+100;
            m=k;
        }
        if(n<10)
		{
			printf("0\n");
			continue;
		}
        sa.s[n-1]=0;
        sa.n=n+1;
        sa.build_sa(200);
        sa.build_height();
        if(!sa.check(4))
		{
			printf("0\n");
			continue;
		}
        int min=1,max=sa.n/2;
        int ans;
        while(min<=max)
        {
            int mid=(max+min)>>1;
            if(sa.check(mid))
			{
				ans=mid;
				min = mid+1;
			}
            else max=mid-1;
        }
        printf("%d\n",ans+1);
    }
    return 0;
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值