【后缀数组】poj1743Musical Theme

推荐阅读《后缀数组——处理字符串的有力工具》。
后缀数组的主要目的是将每一个后缀提取出来,按字典序排序,并求出height数组(当前排名为i的后缀与排名为i-1的后缀的最长公共前缀)。

后缀数组的排序是基于基数排序。

bool cmp(int num[],int a,int b,int len)
{return num[a]==num[b]&&num[a+len]==num[b+len];}

void work(int n,int m)//此处的n为len+1,即在字符串末添上一个0
{
    int *r=tmpa, *pos=tmpb;
    for(int i=0;i<m;++i)cnt[i]=0;
    for(int i=0;i<n;++i)++cnt[r[i]=num[i]];
    for(int i=1;i<m;++i)cnt[i]+=cnt[i-1];
    for(int i=n-1;i>=0;--i)sa[--cnt[r[i]]]=i;

    for(int len=1, tmp=0;tmp<n;m=tmp, len*=2)
    {
        tmp=0;
        for(int i=n-len;i<n;++i)
            pos[tmp++]=i;
        for(inti=0;i<n;++i)
            if(sa[i]>=len)
                pos[tmp++]=sa[i]-len;

        for(int i=0;i<n;++i)
            temp[i]=r[pos[i]];
        for(int i=0;i<m;++i)
            cnt[i]=0;
        for(int i=0;i<n;++i)
            ++cnt[temp[i]];
        for(int i=1;i<m;++i)
            cnt[i]+=cnt[i-1];
        for(int i=n-1;i>=0;--i)
            sa[--cnt[temp[i]]]=pos[i];

        swap(r,pos);
        r[sa[0]]=0, tmp=1;
        for(int i=1;i<n;++i)
            r[sa[i]]=cmp(pos,sa[i],sa[i-1],len)?tmp-1:tmp++;
    }
}

void calh(int n)
{
    for(int i=1;i<=n;++i)Rank[sa[i]]=i;
    for(int i=0, k=0, j;i<n;height[Rank[i++]]=k)
        for(k?--k:0, j=sa[Rank[i]-1];num[i+k]==num[j+k];++k);
}

poj1743
这道题在论文中有详细讲述,但有一个巨坑木有被提到。
在此先膜膜nodgd大神,感谢他为蒟蒻指明了这个巨坑。
请试一试以下数据,若正确就恭喜你了
11
1 2 3 5 4 1 2 3 5 4 1
22
1 2 3 2 1 2 3 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1
0
输出:
5
7

[i1,i2] [i2,i3] 是题目中的两个重复子串,由于先算出差分数组,在差分数组中这两个子串就没有重叠了。
所以尤其要注意 check 函数的写法。

#include <iostream>
#include <cstdio>
#define MAXN 500005
using namespace std;

int n, num[MAXN];
int sa[MAXN], Rank[MAXN], height[MAXN], cnt[MAXN];
int tmpa[MAXN], tmpb[MAXN], temp[MAXN];
int l, r, mid;

bool cmp(int num[],int a,int b,int len)
{return num[a]==num[b]&&num[a+len]==num[b+len];}

void work(int n,int m)
{
    int *r=tmpa, *pos=tmpb;
    for(int i=0;i<m;++i)cnt[i]=0;
    for(int i=0;i<n;++i)++cnt[r[i]=num[i]];
    for(int i=1;i<m;++i)cnt[i]+=cnt[i-1];
    for(int i=n-1;i>=0;--i)sa[--cnt[r[i]]]=i;

    for(int len=1, tmp=0;tmp<n;m=tmp, len*=2)
    {
        tmp=0;
        for(int i=n-len;i<n;++i)pos[tmp++]=i;
        for(int i=0;i<n;++i)if(sa[i]>=len)pos[tmp++]=sa[i]-len;

        for(int i=0;i<n;++i)temp[i]=r[pos[i]];
        for(int i=0;i<m;++i)cnt[i]=0;
        for(int i=0;i<n;++i)++cnt[temp[i]];
        for(int i=1;i<m;++i)cnt[i]+=cnt[i-1];
        for(int i=n-1;i>=0;--i)sa[--cnt[temp[i]]]=pos[i];

        swap(r,pos);
        r[sa[0]]=0, tmp=1;
        for(int i=1;i<n;++i)
            r[sa[i]]=cmp(pos,sa[i],sa[i-1],len)?tmp-1:tmp++;
    }
}

void calh(int n)
{
    for(int i=1;i<=n;++i)Rank[sa[i]]=i;
    for(int i=0, k=0, j;i<n;height[Rank[i++]]=k)
        for(k?--k:0, j=sa[Rank[i]-1];num[i+k]==num[j+k];++k);
}

bool check(int mid)
{
    int i=1, b, e;
    while(i<=n)
    {
        while(i<=n&&height[i]<mid)++i;
        if(i>n) break;
        b=e=sa[i-1];
        while(i<=n&&height[i]>=mid)
        {
            b=min(b,sa[i]), e=max(e,sa[i]);
            ++i;
        }
        if(e-b>mid)return 1;
        //特别注意不能加上=
    }
    return 0;
}

int main()
{
    while(~scanf("%d",&n)&&n)
    {
        for(int i=0;i<n;++i)scanf("%d",&num[i]);
        if(n<10)
        {
            puts("0");
            continue;
        }
        --n;
        for(int i=0;i<n;++i)
            num[i]=num[i+1]-num[i]+89;
        num[n]=0;
        work(n+1,180);
        calh(n);
        l=0, r=n;
        while(l<r)
        {
            mid=(l+r+1)/2;
            if(check(mid))l=mid;
            else r=mid-1;
        }
        if(l<4)puts("0");
        else printf("%d\n",l+1);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值