测试地址:Musical Theme
题目大意:一段旋律有N个音符,音符为1~88之间的一个整数,我们规定旋律的主题为旋律中一个连续的子串,且满足以下要求:1.至少包含5个音符;2.在旋律的另外一个地方也出现(可能会变调,即每一个音符都加上同一个整数);3.和另外一个出现的地方不重合(即两个子串没有相交部分,如[1,2]和[3,4]不重合,[1,2]和[2,3]重合)。给你一段旋律,求出旋律中最长的主题的长度,如果没有主题则输出0。
做法:看上去非常高大上啊,好久没写后缀数组手生了,活活写了3h才过,要命啊......一开始没有看懂题目的意思,以为就是求两个不重和的相等子串,结果手测数据发现不对劲,还是看了discuss才发现还有变调这一说......以后要注意......
行了不说废话了,我们知道一个主题无论怎么变调,音符两两之间的差值都是不变的,所以我们先给相邻的两个音符做差,形成新的数列(长度为N-1),给差值从前往后标号为1~N-1,可以知道这个新的数列上的每一个连续子串[i,j]都唯一对应着原来的一段旋律[i,j+1]。如果在差值数列上有两个子串相等,那么这两个子串所对应的原数列上的两段就是通过变调形成的,那么问题就转化为求差值数列上不重合的两个相等子串。由于题目要求最长的主题,我们可以知道答案是单调的,所以我们可以二分主题长度,问题变为判定性问题。要注意的是,在原数列上长度为k的一段,在差值数列上长度为k-1,我们接下来要二分的是差值数列上的长度。
我们对差值数列跑一遍后缀数组,求出height数组。然后二分长度k,根据height数组的值给后缀分组,保证每一组内的后缀两两之间的LCP不小于k,我们知道这样分组的话,每一组后缀在SA上都是一个连续的区间。如果存在长为k的不重合相等子串,充要条件是存在同一组内的两个前缀起点的差值大于等于k,只不过这里有一点要注意,差值数列上不重合的子串,它们对应的原数列中的子串不一定不重合,例如差值数列上[1,2]和[3,4]不重合,然而对应原数列的[1,3]和[3,5]就重合了,所以这里起点差值要大于k才合法。如果合法则说明长为k的不重合相等子串存在,如果每一组都不存在这样的后缀则说明不存在。
最后先把二分出的答案+1,如果小于5则输出0,否则直接输出答案即可。
以下是本人(奇丑无比)的代码(为了节约空间,求后缀数组的过程中不同步骤中相同数组的意义不一定相同,所以才说丑(摔)):
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int n,a[20010],x[50010],y[20010],s[20010],height[20010];
int t[20010],rank[20010],sa[20010];
void DA() //倍增求rank和SA数组
{
int p=1,up=180;
memset(x,0,sizeof(x));
for(int i=1;i<=n;i++) x[i]=a[i];
while(p<n)
{
memset(t,0,sizeof(t));
for(int i=1;i<=n;i++) y[i]=x[i+p]; //赋值第二关键字
for(int i=1;i<=n;i++) t[y[i]]++;
for(int i=1;i<=up;i++) t[i]+=t[i-1];
for(int i=up;i>=1;i--) t[i]=t[i-1];
t[0]=0;
for(int i=1;i<=n;i++) s[++t[y[i]]]=i; //第一次基数排序
memset(t,0,sizeof(t));
for(int i=1;i<=n;i++) t[x[s[i]]]++;
for(int i=1;i<=up;i++) t[i]+=t[i-1];
for(int i=up;i>=1;i--) t[i]=t[i-1];
for(int i=1;i<=n;i++) y[++t[x[s[i]]]]=s[i]; //第二次基数排序
up=1;
s[y[1]]=1;
for(int i=2;i<=n;i++)
{
if (x[y[i]]!=x[y[i-1]]||x[y[i]+p]!=x[y[i-1]+p]) up++;
s[y[i]]=up;
}
for(int i=1;i<=n;i++) x[i]=s[i];
p<<=1;
}
for(int i=1;i<=n;i++)
{
rank[i]=x[i];
sa[rank[i]]=i;
}
}
void calc_height() //求height数组
{
for(int i=1,j=0;i<=n;i++)
{
if (rank[i]==1) continue;
while(a[i+j]==a[sa[rank[i]-1]+j]) j++;
height[rank[i]]=j;
if (j>0) j--;
}
}
bool check(int k) //检查是否存在长为k的不重合相等子串
{
int mx=0,mn=1000000000;
for(int i=1;i<=n;i++)
{
mx=max(mx,sa[i]);
mn=min(mn,sa[i]);
if (height[i+1]<k)
{
if (mx-mn>k) return 1;
mx=0,mn=1000000000;
}
}
return 0;
}
int main()
{
while(scanf("%d",&n)&&n)
{
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
if (i>1) a[i-1]=a[i]-a[i-1]+88;
}
a[n]=height[n]=0;
n--;
DA();
calc_height();
int l=4,r=n-1,ans=0;
while(l<=r)
{
int mid=(l+r)>>1;
if (check(mid))
{
ans=max(ans,mid);
l=mid+1;
}
else r=mid-1;
}
printf("%d
",ans?ans+1:0);
}
return 0;
}