【POJ No. 1743】音乐主题 Musical Theme
北大OJ 题目地址
【题意】音乐旋律被表示为N (1≤N ≤20000)个音符的序列,它们是[1, 88]内的整数,每个音符都代表钢琴上的一个键。许多作曲家都围绕一个重复的主题谱写音乐,该主题属于整个旋律的子序列。旋律的子序列是一个主题,若满足至少5个音符而且在音乐片段的其他地方再次出现(不重叠,但可能存在转换,转换是指该子序列中的每个音符都同时加上或减去一个值),则给定一个旋律,计算最长主题的长度(音符数)。
【输入输出】
输入:
输入包含多个测试用例,每个测试用例的第1行都包含整数N 。以下N 个整数表示音符序列。最后一个测试用例后跟一个0。
输出:
对每个测试用例,都单行输出最长主题的长度。若没有主题,则输出0。
【样例】
【思路分析】
这道题求解的是不重叠、长度大于或等于5的最长重复子串的长度,可以先转变为子串问题,再采用后缀数组及二分法求解。
因为主题子序列可能同时加上或减去一个数,如34 30 26 22 18,若同时加上48,则转换为82 78 74 70 66,因此可以将数字序列逐项求差,转变为普通的子串问题。在差值序列上求解不重叠、长度大于或等于4的最长重复子串的长度ans,因为求差序列比原序列长度少1,所以需要输出ans+1。
例如,对输入样例数据逐项求差后(从第2个开始,每个数都减去前一个数),序列如下:
不重叠长度大于或等于4的最长重复子串为-4-4-4-4,其长度为4,原序列是34 30 26 22 18,长度为5。
【算法设计】
① 逐项求差,将问题转变为普通的求子串问题。
② 求解sa数组。
③ 求解rank数组和height数组。
④ 使用二分法求解,对特定的长度mid,判断是否满足height[i ]≥mid,且sa的最大、最小差值也大于或等于mid(保证不重叠)。
【算法实现】
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=20010;
const int maxm=200;
int n,k;
int s[maxn],ss[maxn],sa[maxn],rank[maxn],height[maxn];;
int wa[maxn],wb[maxn],wv[maxn],c[maxm];
int cmp(int *r,int a,int b,int l){
return r[a]==r[b]&&r[a+l]==r[b+l];
}
void da(int *r,int *sa,int n,int m){
int i,k,p,*x=wa,*y=wb;
for(i=0;i<m;i++)
c[i]=0;
for(i=0;i<n;i++)
c[x[i]=r[i]]++;
for(i=1;i<m;i++)
c[i]+=c[i-1];
for(i=n-1;i>=0;i--)
sa[--c[x[i]]]=i;
for(k=1;k<=n;k<<=1){
//直接利用sa排序第二关键字
p=0;
for(i=n-k;i<n;i++)
y[p++]=i;//补零的位置下标排在最前面
for(i=0;i<n;i++)
if(sa[i]>=k)
y[p++]=sa[i]-k;
//基数排序第一关键字
for(i=0;i<n;i++)
wv[i]=x[y[i]];//将第二关键字排序结果转换为名次,进行排序
for(i=0;i<m;i++)
c[i]=0;
for(i=0;i<n;i++)
c[wv[i]]++;
for(i=1;i<m;i++)
c[i]+=c[i-1];
for(i=n-1;i>=0;i--)
sa[--c[wv[i]]]=y[i];
//根据sa和x数组,重新计算新的x数组
swap(x,y);//y数组已经没有用,更新x需要使用x本身数据,因此放入y使用
p=1,x[sa[0]]=0;
for(i=1;i<n;i++)
x[sa[i]]=cmp(y,sa[i-1],sa[i],k)?p-1:p++;
if(p>=n)//排序结束
break;
m=p;
}
}
void calheight(int *r,int *sa,int n){
int i,j,k=0;
for(i=1;i<=n;i++)
rank[sa[i]]=i;
for(i=0;i<n;i++){
if(k)
k--;
j=sa[rank[i]-1];
while(r[i+k]==r[j+k])
k++;
height[rank[i]]=k;
}
}
bool check(int mid){
int mx=sa[1],mn=sa[1];
for(int i=2;i<=n;i++){
if(height[i]>=mid){
mx=max(mx,sa[i]);
mn=min(mn,sa[i]);
if(mx-mn>=mid)
return 1;
}
else{
mx=sa[i];
mn=sa[i];
}
}
return 0;
}
void solve(){
int L=4,R=n,res=-1;
while(L<=R){
int mid=(L+R)>>1;
if(check(mid)){
res=mid;
L=mid+1;
}
else
R=mid-1;
}
if(res<4)
printf("0\n");
else
printf("%d\n",res+1);
}
int main(){
while(~scanf("%d",&n),n){
for(int i=0;i<n;i++)
scanf("%d",&s[i]);
if(n<9){
printf("0\n");
continue;
}
n--;
for(int i=0;i<n;i++)
ss[i]=s[i+1]-s[i]+100;
ss[n]=0;
da(ss,sa,n+1,200);
calheight(ss,sa,n);
solve();
}
return 0;
}