[POJ 1743]Musical Theme
[POJ 1743]Musical Theme
\(Description:\)
一个长度为n的数列,问是否存在长度大于等于5的相似不重叠子列
若存在则求最长的不重叠的相似子列,若不存在则输出0
相似的定义为相应位置的差值为常数(如:1,2,5和3,4,6相似)
\(Solution:\)
将原数组进行差分,这样相似的子列就变成了差分数组中相同的子列
原题目就变成了在差分数组中求最长的不重叠的相等子列
注意,差分为相邻两个数的差值,因此差分后数组的长度为n-1,且原数组中长度为x的相似子列对应差分数组中长度为x-1的相等子列
如果不考虑是否重叠的问题,那么这是一个经典的LCP(最长公共前缀)问题,因为每一个子串都是某个后缀的前缀
该问题用后缀数组处理出height数组可以解决
如果要求不重叠,我们对答案进行二分,对于每一次二分出来的答案,我们遍历每一个连续的height>=mid的区间,这段区间的字符串两两之间的最长公共前缀>=mid,在这段区间中找最靠前和最靠后的,计算两者的距离dis,如果存在某一个区间中的字符串dis>mid,则当前答案成立
\(Code:\)
#include<bits/stdc++.h>
using namespace std;
const int N=20005;
int s[N],ht[N],n,m,sa[N],x[N*2],y[N*2],c[N],rk[N];
void get_sa(){
memset(c,0,sizeof(c));
for(int i=1;i<=n;i++)c[x[i]=s[i]]++;
for(int i=2;i<=m;i++)c[i]+=c[i-1];
for(int i=n;i>=1;i--)sa[c[x[i]]--]=i;
for(int w=1;w<=n;w<<=1){
int num=0;
for(int i=n-w+1;i<=n;i++)y[++num]=i;
for(int i=1;i<=n;i++)if(sa[i]>w)y[++num]=sa[i]-w;
for(int i=1;i<=m;i++)c[i]=0;
for(int i=1;i<=n;i++)c[x[i]]++;
for(int i=2;i<=m;i++)c[i]+=c[i-1];
for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i];
swap(x,y);x[sa[1]]=num=1;
for(int i=2;i<=n;i++)
x[sa[i]]=(y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+w]==y[sa[i]+w])?num:++num;
if((m=num)==n)break;
}
}
bool check(int mid){
int mmax,mmin;
mmax=mmin=sa[1];
for(int i=2;i<=n;i++){
if(ht[i]>=mid){
mmax=max(mmax,sa[i]);
mmin=min(mmin,sa[i]);
continue;
}
if(mmax-mmin>mid)return 1;
else mmax=mmin=sa[i];
}
return mmax-mmin>mid;
}
int main(){
while(scanf("%d",&n)!=EOF&&n){
m=300;
for(int i=1;i<=n;i++)scanf("%d",&s[i]);n--;
for(int i=1;i<=n;i++)s[i]=s[i+1]-s[i]+88;
get_sa();
memset(ht,0,sizeof(ht));
for(int i=1;i<=n;i++)rk[sa[i]]=i;
for(int k=0,i=1;i<=n;i++){
if(k)k--;
while(s[i+k]==s[sa[rk[i]-1]+k])k++;
ht[rk[i]]=k;
}
int l=4,r=n,ans=-1;
while(l<=r){
int mid=(l+r)>>1;
if(check(mid)){ans=mid;l=mid+1;}
else r=mid-1;
}
printf("%d\n",ans+1);
}
return 0;
}