给你一串数字,求它们最长的重复(公差相同)子序列,且两个子序列不相交
大致思路:后缀数组+二分答案
问题:
1、将公差相同的子串转化为重复子串问题
比如说原序列为:a1 a2 a3 a4 a5 a6 a7
如果子串a3a4,a6a7是公差相同的子串,则a7-a4==a6-a3
移项:a7-a6=a4-a3 相邻的差相同?
于是想到原序列的差分序列:
d1 d2 d3 d4 d5 d6 d7(d1=a1-a0,d2=a2-a1……)
若 d2,d3和d6,d7为重复子串,则
d6=d2,d7=d3
a6-a5=a2-a1
a7-a6=a3-a2,移项:
a6-a2=a5-a1
a7-a3=a6-a2,则
a1a2a3和a5a6a7是原串的公差相同的子串(长度+1)
这样就转化成了求差分序列的后缀数组问题
2、二分答案,判断当前长度是否可行
O(logn)二分枚举子串长度,判断解是否成立
O(n)判断长度是否成立:把互相之间LCP大于等于长度的分为一组,这通过个扫一遍height即可,因为后缀是有序的,相邻的后缀间的LCP必定的极大的(若相隔多个则取最小值);接下来就找到每个组里后缀sa值最大和最小的,如果差值大于k就成立,因为这样小下标的后缀沿着LCP下去走k步才不会盖到大下标的后缀。
下面附上代码:
#include "iostream"
#include "cstdio"
#include "cstring"
#include "string"
#include "algorithm"
#define maxn 20005
using namespace std;
int s[maxn],a[maxn];
int sa[maxn],se[maxn],rk[maxn],x[maxn],height[maxn],tank[maxn],n,m;
void build(){
m=400;
for(int i=0;i<=m;i++) tank[i]=0;
for(int i=1;i<=n;i++) tank[x[i]=a[i]]++;
for(int i=1;i<=m;i++) tank[i]+=tank[i-1];
for(int i=n;i>=1;i--) sa[tank[a[i]]--]=i;
for(int j=1;j<=n;j<<=1){
int p=0;
for(int i=n-j+1;i<=n;i++) se[++p]=i;
for(int i=1;i<=n;i++) if(sa[i]>j) se[++p]=sa[i]-j;//第二关键字已经有序;
for(int i=0;i<=m;i++) tank[i]=0;
for(int i=1;i<=n;i++) tank[x[se[i]]]++;
for(int i=1;i<=m;i++) tank[i]+=tank[i-1];
for(int i=n;i>=1;i--) sa[tank[x[se[i]]]--]=se[i];
swap(se,x);
x[sa[1]]=1;p=1;
for(int i=2;i<=n;i++){
x[sa[i]]=se[sa[i]]==se[sa[i-1]]&&se[sa[i]+j]==se[sa[i-1]+j]?p:++p;
}
if(p>=n) break;
m=p;
}
}
void get_height(){
for(int i=1;i<=n;i++) rk[sa[i]]=i;
int k=0;
for(int i=1;i<=n;i++){
if(rk[i]==1) continue;
if(k) k--;
int j=sa[rk[i]-1];
while(i+k<=n&&j+k<=n&&a[i+k]==a[j+k]) ++k;
height[rk[i]]=k;
}
}
int judge(int len){//二分长度
int mx=0,mn=0x3f3f3f3f;
for(int i=2;i<=n;i++){
if(height[i]>=len){
mx=max(mx,max(sa[i],sa[i-1]));
mn=min(mn,min(sa[i],sa[i-1]));
if(mx-mn>len) return 1;
}
else{
mx=0,mn=0x3f3f3f3f;
}
}
return 0;
}
int main(){
while(scanf("%d",&n)&&n){
s[0]=-200;
for(int i=1;i<=n;i++) scanf("%d",&s[i]);
for(int i=1;i<=n;i++){
a[i]=s[i]-s[i-1];
a[i]+=90;
}//做差很妙!!!
build();
get_height();
int l=0,r=n;
while(l<r){
int mid=l+r+1>>1;
if(judge(mid)) l=mid;
else r=mid-1;
}
if(l+1>=5) printf("%d\n",l+1);
else printf("%d\n",0);
}
return 0;
}
本文介绍了一种利用后缀数组和二分查找算法解决最长重复(公差相同)子序列问题的方法,通过将原问题转化为差分序列的后缀数组问题,并使用二分查找判断子串长度的可行性。
3408

被折叠的 条评论
为什么被折叠?



