题目链接:http://poj.org/problem?id=1743
题目大意:给你一段长度为(1<=n<=20000)的音符:每个音符是1…88范围内的整数,每个音符代表钢琴上的一个键,

一个旋律的子序列满足下面三个条件:

1.是至少五个音符长
2.在音乐片段的其他地方再次出现(可能转换 - 见下文)
3.与其至少一个其他外观不相交(即,不重叠)

转换:将恒定的正值或负值添加到主题子序列中的每个音符值。


这个题不能直接对数进行哈希,因为可能同时加减了一个数。但是相邻两个数的差是不变的,所以要对差进行哈希。

然后二分长度。对每个长度再枚举子串,再左右查询,是否存在这个字符串,对于一个长度,有n个子串,对于每个子串,每次查询都是O(n)的复杂度。二分log你个长度,时间复杂度:O(n* n *logn)

1<=n<=20000肯定T了。但是:你会发现:枚举和查询是有重复的地方,这次枚举这个串,之前查询过。其实我们可把枚举和查询合并。

用map

每次二分出长度Len。从i=0开始,得到一个长度为n的字符串,得到哈希值,r如果map[hash]=0,然后加入map[hash]=i, 如果map[hash]!=0说明之前已经出现过这个字符串,但是题目要求两个子串不重叠,

这个时候我们只有判断 i - map[hash]>=Len?如果是,那么说明两个字符串的距离>=Len一定不会重叠。
时间复杂度:O(nlognlogn)然后还是T,我现在已经无法优化了。

TLE:
#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const int maxn=20006;

ull base =131;
ull g[maxn];
ull p[maxn];
int n;

int a[20005], b[20005];

ull Hash(int s[], int n)
{
    ull ans=0;

    g[0]=s[0];
    for(int i=1;i<n;i++)
    {
        g[i]=g[i-1]*base+s[i];
    }
    return g[n-1];
}

void getp()
{
    p[0]=1;
    for(int i=1;i<=maxn;i++)
    {
        p[i]=p[i-1]*base;
    }
}

ull getLR(int l, int r)//取出T里l - r里面的字符串的hash值
{
    return g[r]-g[l-1]*p[r-l+1];
}

map<ull, int> mp;
int dfs(int Len)
{
    mp.clear();
    for(int i=1;i<=n-Len+1;i++)
    {
        ull now=getLR(i, i+Len-2);
        int k=mp[now];
        if(k)
        {
            if(i-k>=Len)
            {
                return 1;
            }
        }
        else
        {
            mp[now]=i;
        }
    }
    return 0;
}

int main()
{
    getp();
    while(scanf("%d",&n),n)
    {
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
        }
        for(int i=1;i<n;i++)
        {
            b[i]=a[i+1]-a[i];
        }
        Hash(b, n);
        int L=1, R=(n)/2, k=0;
        while(L<=R)
        {
            int Len=(L+R)/2, flog=0;

            if(dfs(Len))
            {
                L=Len+1, k=Len;
            }
            else
            {
                R=Len-1;
            }
        }
        printf("%d\n", k);
    }

    return 0;
}