题意:有N个音符组成的一首乐曲,每个音符是一个1~88的整数,现在要找一个最长的主题,即是该音符序列的一个子串,主题还要满足以下三个条件:
1.最少由五个音符组成
2.该主题在乐曲中出现最少两次(两个主题不一定要一模一样,只要一个可以由另个一转置得到即视为同一种主题,转置:主题序列中的每一个字符都被加上或者减去了同一个数字)
3.每两个主题不可有重叠的部分
如果没有装置这个要求的话,很显然是求不可重叠最长重复子串,用后缀数组就可以做,但是我们观察转置的定义,经过一些处理可以将题目化为不可重复最长重复子串。假设一个子串是a,b,c,d,e.另一个子串是a+k,b+k,c+k,d+k,e+k.显然他们是同一个主题,我们发现将他们对每相邻两个字符求差值得到的差值串是一样的。
a,b,c,d,e -> b-a,c-b,d-c,e-d
a+k,b+k,c+k,d+k -> b-a,c-b,d-c,e-d
于是可以将原序列每相邻连个求差值得到新序列,然后求其后缀数组。但是由于化成新序列之后字符数量减少1.我们找的主题变成了最少由四个字符组成就可以,但是这里面有个小问题,比如
1 1 1 1 1 1 1 1 1 ----->0 0 0 0 0 0 0 0这个显然是不满足要求的,因此要求两个主题之间至少要相差一个字符,这样就可以解决这个问题了
"不可重叠最长重复子串"解法(摘自罗穗骞《后缀数组——处理字符串的有力工具》):
先二分答案,把题目变成判定性问题:判断是否存在两个长度为k的子串是相同的,且不重叠。解决这个问题的关键还是利用height数组(height在代码中有解释)。把排序后的后缀分成若干组,其中每组的后缀之间的height值都不小于k。例如,字符串为“aabaaaab”,当k=2时,后缀分成了4组,如图所示
容易看出,有希望成为最长公共前缀不小于k的两个后缀一定在同一组。然后对于每组后缀,只须判断每个后缀的sa值的最大值和最小值之差是否不小于k。如果有一组满足,则说明存在,否则不存在。整个做法的时间复杂度为O(nlogn)
#pragma GCC optimize(2)
//#include<bits/stdc++.h>
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
typedef unsigned long ul;
typedef unsigned long long ull;
#define pi acos(-1.0)
#define e exp(1.0)
#define pb push_back
#define mk make_pair
#define fir first
#define sec second
//#define scf scanf
//#define prf printf
typedef pair<ll,ll> pa;
const ll INF=0x3f3f3f3f3f3f3f3f;
const ll maxn=2e5+7;
ll height[maxn],sa[maxn],rank[maxn],tmp[maxn],r[maxn],K,N;
bool cmp(ll i,ll j){
//i,j是后缀开始的位置
if(rank[i]!=rank[j])
return rank[i]<rank[j];
ll r1=i+K<=N?rank[i+K]:-1;//i+K==N 相当于空串
ll r2=j+K<=N?rank[j+K]:-1;
return r1<r2;
}
//倍增法求后缀数组
void do_sa(){
ll i,j;
//给rank[]和sa[]数组赋值,
for(i=0;i<=N;i++){
sa[i]=i;
// rank[i]=(i==N?-1:r[i]);//包含空串
rank[i]=r[i];
}
//倍增法求后缀树组
for(K=1;K<=N;K<<=1){
sort(sa,sa+1+N,cmp);
tmp[sa[0]]=0;//最小一定是空串
for(i=1;i<=N;i++)
tmp[sa[i]]=tmp[sa[i-1]]+(cmp(sa[i-1],sa[i])?1:0);
//sa[i]和sa[i-1]的顺序千万别弄反了,和上面的cmp函数的定义是对应的,如果写反,当r1==r2是不等价的
for(i=0;i<=N;i++)
rank[i]=tmp[i];
}
return ;
}
//height[i]数组存储从i处开始的后缀字符串和排名小于当相邻的位置j处开始的后缀
//字符串子串的最长公共子串长度
void get_height(){
ll i,j,k=0;
for(i=0;i<N;i++){
//
if(k)
k--;
else
k=0;
j=sa[rank[i]-1];
while(r[i+k]==r[j+k])
k++;
height[rank[i]]=k;
}
return ;
}
bool check(ll mid){
ll i,j;
ll maxx=-INF,minn=INF;
for(i=1;i<=N;i++){
if(height[i]>=mid){
minn=min(minn,min(sa[i],sa[i-1]));
maxx=max(maxx,max(sa[i],sa[i-1]));
if(maxx-minn>mid) return true;
}
else{
maxx=-INF;
minn=INF;
}
}
return false;
}
int main()
{
// freopen(".../.txt","w",stdout);
// freopen(".../.txt","r",stdin);
// ios::sync_with_stdio(false);
while(scanf("%lld",&N)&&N){
ll i,j,k;
// memset(height,0,sizeof(height));
// memset(rank,0,sizeof(rank));
// memset(tmp,0,sizeof(tmp));
// memset(sa,0,sizeof(sa));
// memset(r,0,sizeof(r));
//求差值串,r[]相当于字符串
for(i=0;i<N;i++){
scanf("%lld",&r[i]);
if(i)
r[i-1]=r[i]-r[i-1]+88;
}
N--;
r[N]=0;
do_sa();
get_height();
ll L=0,R=N/2,mid,res=0;
while(L<=R){
mid=(R-L)/2+L;
if(check(mid)){
L=mid+1;
res=max(res,mid);
}
else{
R=mid-1;
}
}
if(res<4){
printf("0\n");
continue;
}
printf("%lld\n",res+1);
}
return 0;
}
上面使用的后缀数组模板使用的《挑战程序设计竞赛》,书上对后缀数组的讲解的截图如下
由于在调用cmp函数时两个参数的额传入顺序写反了,导致对后缀子串排序错误,害我郁闷了很久……