题意:有N(1 <= N <=20000)个音符的序列来表示一首乐曲,每个音符都是1..88范围内的整数,现在要找一个重复的主题。“主题”是整个音符序列的一个子串,它需要满足如下条件:
1.长度至少为5个音符。
2.在乐曲中重复出现。(可能经过转调,“转调”的意思是主题序列中每个音符都被加上或减去了同一个整数值)
3.重复出现的同一主题不能有公共部分。
/* 改了三个小时,还是没有改出来,WA到挺了。 说一下解题过程吧。 二分答案,然后按照公共前缀长度是否大于等于mid分组,若有一组中的间距大于mid,则说明找到了一组符合要求的。 */ #include<cstdio> #include<iostream> #include<cstring> #define N 51000 using namespace std; int ch[N],s[N],sa[N],rank[N],height[N],t1[N],t2[N],c[N]; bool cmp(int *y,int a,int b,int k){ return y[a]==y[b]&&y[a+k]==y[b+k]; } void DA(int n,int m){ int *x=t1,*y=t2; 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;i--) sa[--c[x[i]]]=i; for(int k=1,p=0;k<=n;k*=2,m=p,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;i--) sa[--c[x[y[i]]]]=y[i]; swap(x,y);p=1;x[sa[0]]=0; for(int i=1;i<n;i++) if(cmp(y,sa[i],sa[i-1],k)) x[sa[i]]=p-1; else x[sa[i]]=p++; if(p>=n) break; } for(int i=0;i<n;i++) rank[sa[i]]=i; } void get_ht(int n){ for(int i=1,k=0;i<n;height[rank[i++]]=k){ int j=sa[rank[i]-1];k=k?k-1:0; while(s[j+k]==s[i+k]) k++; } } bool check(int n,int mid){ int minn=n+1,maxn=-1; for(int i=1;i<=n;i++){ if(height[i]<mid){ if(maxn-minn>mid) return true; minn=n+1;maxn=-1; } minn=min(minn,sa[i]); maxn=max(maxn,sa[i]); } if(maxn-minn>mid) return true; return false; } void CL(){ memset(s,0,sizeof(s)); memset(sa,0,sizeof(sa)); memset(rank,0,sizeof(rank)); memset(height,0,sizeof(height)); memset(t1,0,sizeof(t1)); memset(t2,0,sizeof(t2)); memset(ch,0,sizeof(ch)); memset(c,0,sizeof(c)); } int main(){ int n; while(scanf("%d",&n)&&n){ CL(); for(int i=0;i<n;i++) scanf("%d",&ch[i]); for(int i=0;i<n-1;i++) s[i]=ch[i+1]-ch[i]+100; s[n-1]=0; if(n==1){printf("0 ");continue;} DA(n+1,400);get_ht(n); int l=0,r=n,ans; while(l<=r){ int mid=l+r>>1; if(check(n,mid)) l=mid+1,ans=mid; else r=mid-1; } ans++; ans=ans<5?0:ans; printf("%d ",ans); } return 0; }