A - Musical Theme (求不可重叠的最长重复子串,二分+后缀数组)

A musical melody is represented as a sequence of N (1<=N<=20000)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 &qout;theme&qout;, 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: 

  • is at least five notes long 
  • appears (potentially transposed -- see below) again somewhere else in the piece of music 
  • 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! 

Input

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. 

Output

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.

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

Hint

Use scanf instead of cin to reduce the read time.

题意:给出一串数字,每个范围是1-88,代表某个音符,要求找出一个最长的主旋律串,所谓主旋律串是一个子串,要求个数大于5,至少出现两次(这里如果另一个串是这个串的每个数字加上一个常数,则也算该串出现了两次),并且串之间不重叠。求出最长的主旋律串

思路:

  • 先对原串做处理,一个串如果是另一个串加上一个常数后的结果,那么这两个串中的相邻元素之间差值是一样的。根据这一点,预处理原串的相邻元素之间的差值(这里再加上90,使得差值都是正数,方便后面后缀数组的基数排序)。
  • 则原题就变成了找出预处理后的串中最长的不可重叠的重复子串。直接后缀数组求出height,然后二分长度k,遍历height,看看有没有height值大于等于k,并且这两个串之间的距离大于k的,注意这里是大于k,等于不行,因为我们求出的串是相邻元素的差值,如果两个串距离等于k,那么转到原串后,前一个串的最后一个元素和后一个串的首元素是重叠的。所以不行。这里注意一下,自己拿笔画画应该就明白了。
  • 注意到对于Height数组,如果连续一段区间的值都大于k,说明这些串的公共前缀都是大于k的,这样我们包保存这些串中下标最小的和最大的,这样能使两个串之间的距离尽可能大,也就尽可能是不重叠的。这是check函数的思路。注意一下。当然,如果枚举到一个height小于k了,最大和最小的下表就得重定位到当前位置。

 

#include<iostream>
#include<queue>
#include<cstring>
#include<math.h>
#include<algorithm>
#include<cstdio>
#include<map>
#include<stack>
#include<vector>
#define rep(i,e) for(int i=0;i<(e);++i)
#define rep1(i,e) for(int i=1;i<=(e);++i)
#define repx(i,x,e) for(int i=(x);i<=(e);++i)
#define pii pair<int,int>
#define X first
#define Y second
#define PB push_back
#define MP make_pair
#define mset(var,val) memset(var,val,sizeof(var))
#define IOS ios::sync_with_stdio(false);cin.tie(0)
typedef long long ll;
using namespace std;

#ifdef LOCAL
template<typename T>
void dbg(T t){
    cout<<t<<" "<<endl;
}
template<typename T, typename... Args>
void dbg(T t, Args... args){
    cout<<t<<" ";dbg(args...);
}


#else
#define dbg(...)
#endif // local
typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3fll;
const int mod = 1e9+7;
const int N = 1e4+10;
typedef long long ll;
#define K 137
const int maxn = 2e4+10;
const int maxm =4e5+10;

int num[maxn];
int x[maxn],y[maxn],c[maxn];
int rnk[maxn];
int height[maxn],sa[maxn];
void build_sa(int s[],int n,int m){
    int i,j,p;
    for(int i = 0; i < m; i++) c[i] = 0;
    for(int i = 0; i < n; i++) c[x[i] = s[i]]++;
    for(int i = 1; i < m; i++) c[i] += c[i-1];
    for(int i = n-1; i >= 0; i--) sa[--c[x[i]]] = i;

    for(int k = 1; k <= n; k <<= 1){
        p = 0;
        for(int i = n-k; i < n; i++) y[p++] = i;
        for(int i = 0; i < n; i++)
            if(sa[i] >= k)
                y[p++] = sa[i]-k;
        for(int i = 0; i < m; i++) c[i] = 0;
        for(int i = 0; i < n; i++) c[x[y[i]]]++;
        for(int i = 1; i < m; i++) c[i] += c[i-1];
        for(int i = n-1; i >= 0; i--) sa[--c[x[y[i]]]] = y[i];
        swap(x,y);
        p = 1; x[sa[0]] = 0;
        for(int 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 getHeight(int s[],int n){
    int i,j,k = 0;
    for(int i = 0; i <= n; i++)
        rnk[sa[i]] = i;
    for(int i = 0; i < n; i++){
        if(k) k--;
        j = sa[rnk[i]-1];
        while(s[i+k] == s[j+k]) k++;
        height[rnk[i]] = k;
    }
}

bool check(int k,int n){
    int mn,mx;
    mn = mx = sa[1];
    for(int i = 2; i <= n; i++){
        if(height[i] < k)
            mn = mx = sa[i];
        else{
            mn = min(sa[i],mn);
            mx = max(sa[i],mx);
            if(mx - mn > k)
                return true;
        }
    }
    return false;
}

void work(){
    int n;
    while(scanf("%d",&n) && n != 0){
        for(int i = 0; i < n; i++){
            scanf("%d",&num[i]);
        }
        for(int i = n-1; i > 0; i--){
            num[i] = num[i]-num[i-1] + 90;
        }
        n--;
        for(int i = 0; i < n; i++){
            num[i] = num[i+1];
        }

        num[n] = 0;
        build_sa(num,n+1,200);

        getHeight(num,n);
        int l = 1,r = n/2;
        int res = 1;
        while(l <= r){
            int m = (l+r) >> 1;
            if(check(m,n)){
                l = m+1;
                res = max(res,m);
            }
            else
                r = m-1;
        }
        if(res >= 4)
            printf("%d\n",res+1);
        else
            printf("0\n");
    }
}

int main()
{
#ifdef LOCAL
    freopen("in.txt","r",stdin);
#endif // LOCAL
    work();
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值