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

原题题面

A musical melody is represented as a sequence of N ( 1 ≤ N ≤ 20000 ) N(1\leq N\leq 20000) N(1N20000)notes that are integers in the range 1…88, each representing a key on the piano. It is unfortunate but true that this representation of melodies ignores the notion of musical timing; but, this programming task is about notes and not timings.
Many composers structure their music around a repeating “theme”, which, being a subsequence of an entire melody, is a sequence of integers in our representation. A subsequence of a melody is a theme if it:
1.is at least five notes long
2.appears (potentially transposed – see below) again somewhere else in the piece of music
3.is disjoint from (i.e., non-overlapping with) at least one of its other appearance(s)
Transposed means that a constant positive or negative value is added to every note value in the theme subsequence.
Given a melody, compute the length (number of notes) of the longest theme.
One second time limit for this problem’s solutions!

输入格式

The input contains several test cases. The first line of each test case contains the integer N. The following n integers represent the sequence of notes.
The last test case is followed by one zero.

输出格式

For each test case, the output file should contain a single line with a single integer that represents the length of the longest theme. If there are no themes, output 0.

输入样例

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

输出样例

5

题面分析

给定一个用整数构成的串,请找出符合以下条件的子串的最长长度。
1.至少五个数字
2.至少重复出现过一次(注:如果在给这个子串的每一个数都同时加上或减去一个数也是算出现过的)
3.这些重复出现的子串的位置至少一处不重叠。
先差分,排除同时加上/减去一个数的影响,用后缀数组计算出整个串的SA和LCP,然后二分答案求解。
至于check函数的处理,我们考虑寻找一个可能的解,其对应的起始位置为 p o s a , p o s b ( p o s a ≤ p o s b ) posa,posb(posa\leq posb) posa,posb(posaposb),使得这两个后缀串里面的LCP大于等于二分的答案mid。
L C P ( p o s a , p o s b ) = m i n ( L C P ( j , j − 1 ) ) ( 1 < p o s a ≤ j ≤ p o s b ≤ n ) LCP(posa,posb)=min(LCP(j,j-1)) (1<posa\leq j\leq posb\leq n) LCP(posa,posb)=min(LCP(j,j1))(1<posajposbn)
故我们找到一个连续段,使得Height数组的值都大于等于mid,就满足了找到重复串的可能。
接下来是处理如何不重叠,对于 p o s a , p o s b posa,posb posa,posb,在已知公共长度为mid的情况下,我们比较在 [ p o s a , p o s b ] [posa,posb] [posa,posb]之间最大的SA与最小的SA之差是否大于(不是大于等于) mid即可。

AC代码

#include <bits/stdc++.h>
using namespace std;
const int maxn=2e4;
namespace SA
{
    int SA[maxn+10];
    int Rank[maxn+10];
    int Height[maxn+10];
    int tax[maxn+10], tp[maxn+10];
    int len;
    int s[maxn+10];
    int m;//m当前离散后的排名种类数
    void RSort()
    {
        for(int i=0; i<=m; i++) tax[i]=0;
        for(int i=1; i<=len; i++) tax[Rank[tp[i]]]++;
        for(int i=1; i<=m; i++) tax[i]+=tax[i-1];
        for(int i=len; i>=1; i--) SA[tax[Rank[tp[i]]]--]=tp[i];
    }
    int cmp(int *f, int x, int y, int w)
    {
        return f[x]==f[y] && f[x+w]==f[y+w];
    }
    void GetSA()
    {
        for(int i=1; i<=len; i++)
        {
            Rank[i]=s[i], tp[i]=i;
        }
        RSort();
        for(int w=1, p=1, i; p<len; w+=w, m=p)
        {
            for(p=0, i=len-w+1; i<=len; i++)
                tp[++p]=i;
            for(i=1; i<=len; i++)
                if (SA[i]>w)
                    tp[++p]=SA[i]-w;
            RSort(), swap(Rank, tp), Rank[SA[1]]=p=1;
            for(i=2; i<=len; i++)
                Rank[SA[i]]=cmp(tp, SA[i], SA[i-1], w) ? p : ++p;
        }
    }
    void GetLCP()
    {
        int j, k=0;
        for(int i=1; i<=len; Height[Rank[i++]]=k)
            for(k=k ? k-1 : k, j=SA[Rank[i]-1]; s[i+k]==s[j+k]; ++k);
    }
    void PrintSA()
    {
        for(int i=1; i<=len; i++)
        {
            printf("%d ", SA[i]);
        }
        printf("\n");
    }
    void PrintHeight()
    {
        for(int i=1; i<=len; i++)
        {
            printf("%d ", Height[i]);
        }
        printf("\n");
    }
    void PrintRank()
    {
        for(int i=1; i<=len; i++)
        {
            printf("%d ", Rank[i]);
        }
        printf("\n");
    }
}
bool check(int m)
{
    int minx=SA::SA[1];
    int maxx=SA::SA[1];
    for(int i=1; i<=SA::len; i++)
    {
        if (SA::Height[i]<m)
        {
            minx=maxx=SA::SA[i];
        }
        else
        {
            maxx=max(maxx, SA::SA[i]);
            minx=min(minx, SA::SA[i]);
            if (maxx-minx>m)
                return 1;
        }
    }
    return 0;
}
int a[maxn+10];
void solve()
{
    int n;
    while(~scanf("%d", &n) && n)
    {
        a[0]=0;
        for(int i=1; i<=n; i++)
            scanf("%d", &a[i]);
        if (n==1)
        {
            printf("0\n");
            continue;
        }
        for(int i=1; i<=n-1; i++)
        {
            SA::s[i]=a[i+1]-a[i]+100;
        }
        SA::m=200;
        SA::SA[n]=0;
        SA::len=n-1;
        SA::GetSA();
        SA::GetLCP();
        int l=1, r=(n-1)/2;
        int ans=0;
        while(l<=r)
        {
            int mid=(l+r)>>1;
            if (check(mid))
            {
                l=mid+1;
                ans=mid;
            }
            else
            {
                r=mid-1;
            }
        }
        if (ans<4)
            printf("0\n");
        else
            printf("%d\n", ans+1);
    }
}
int main()
{
//    ios_base::sync_with_stdio(false);
//    cin.tie(0);
//    cout.tie(0);
#ifdef ACM_LOCAL
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
    long long test_index_for_debug=1;
    char acm_local_for_debug;
    while(cin>>acm_local_for_debug)
    {
        cin.putback(acm_local_for_debug);
        if (test_index_for_debug>100)
        {
            throw runtime_error("Check the stdin!!!");
        }
        auto start_clock_for_debug=clock();
        solve();
        auto end_clock_for_debug=clock();
        cout<<"\nTest "<<test_index_for_debug<<" successful"<<endl;
        cerr<<"Test "<<test_index_for_debug++<<" Run Time: "
            <<double(end_clock_for_debug-start_clock_for_debug)/CLOCKS_PER_SEC<<"s"<<endl;
        cout<<"--------------------------------------------------"<<endl;
    }
#else
    solve();
#endif
    return 0;
}

后记

第一个后缀数组题,特此记录。
DrGilbert 2020.10.18

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值