POJ - 1743 Musical Theme 后缀数组 二分

题目链接: POJ-1743 Musical Theme

题目大意

一个数组有n个元素, 求最长重复出现(不重叠)的子串的长度, 要求长度大于等于5, 并且如果一个串每个数字加上同一个值等于另一个子串, 视这两个子串相同(1 2 3和4 5 6相同)

思路

相邻元素作差得到另一个数组, 然后就化简成了求一般的最长重复出现的子串长度了
对新数组求高度数组, 高度数组有一个特性, 其大小变化一定是大->小, 大->小…重复出现的.

我们二分求解最长重复子串长度, 因为不能重叠, check时, 对高度数组找出每一个所有元素都大于等于长度mid的区间, 这样, 这个区间内所有后缀公共前缀长度都大于mid, 接下来只要判断是否重叠就好了

在每一个区间中, 找到最大和最小的sa[i], 如果最大-最小 > mid, 说明没有重叠

之所以是>而不是>=, 是因为作差导致新数组的子串都相当于原来数组的子串去掉第一个元素, 例如1, 5, 4, 3, 5作差变成4, -1, -1, 2, 新数组的子串4, -1, -1,相当于1, 5 ,4, 3所以不能等于, 等于了就会导致前面一个子串的最后一个元素和后一个子串的第一个元素重叠

因为我用的是挑战程序设计竞赛上的模板, lcp[i]代表Suffix[sa[i]]和Suffix[sa[i+1]]的最长公共前缀, 和网上的Suffix[sa[i]]和Suffix[sa[i-1]]不同, 没注意到这个, 结果WA了

代码

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int maxn = 2e4 + 1000;
int n, a[maxn], b[maxn];

int rk[maxn], tmp[maxn], sa[maxn], k;
bool cmp_sa(int i, int j)
{
    if (rk[i] != rk[j]) return rk[i] < rk[j];
    return (i + k <= n ? rk[i + k] : -1) < (j + k <= n ? rk[j + k] : -1);
}
void construct_sa(int *s)
{
    for (int i = 0; i <= n; ++i)
    {
        sa[i] = i;
        rk[i] = s[i];
    }
    rk[n] = -1;
    for (k = 1; k <= n; k *= 2)//这里的k是全局变量, 不要在这里定义
    {
        sort(sa, sa + n + 1, cmp_sa);

        tmp[sa[0]] = 0;
        for (int i = 1; i <= n; ++i)
            tmp[sa[i]] = tmp[sa[i - 1]] + (cmp_sa(sa[i - 1], sa[i]) ? 1 : 0);
        copy(tmp, tmp + n + 1, rk);
    }
}

int lcp[maxn];
void construct_lcp(int *s)
{
    for (int i = 0; i <= n; ++i) rk[sa[i]] = i;

    int h = 0;
    lcp[0] = 0;
    for (int i = 0; i < n; ++i)
    {
        int j = sa[rk[i] - 1];

        if (h > 0) --h;
        for (; j + h < n && i + h < n; ++h)
            if (s[j + h] != s[i + h]) break;
        lcp[rk[i] - 1] = h;
    }
}

bool check(int mid)
{
    int mi = sa[n], ma = sa[n];

    for(int i=n-1; i>=0; --i)
    {
        if(lcp[i] < mid) mi = ma = sa[i];
        else 
        {
            mi = min(mi, sa[i]);
            ma = max(ma, sa[i]);
            if(ma-mi > mid) return true;
        }
    }
    return false;
}

int main()
{
    while (scanf("%d", &n) == 1 && n)
    {
        for (int i = 0; i < n; ++i) scanf("%d", a + i);
        for (int i = 1; i < n; ++i) b[i - 1] = a[i] - a[i - 1] + 100;
        --n;

        construct_sa(b);
        construct_lcp(b);

        int l = 1, r = n / 2, mid;
        while (l < r)
        {
            mid = (l + r + 1) / 2;
            if (check(mid)) l = mid;
            else r = mid - 1;
        }
        printf("%d\n", l < 4 ? 0 : l + 1);
    }

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值