POJ 1743 Musical Theme(后缀数组)
http://poj.org/problem?id=1743
题意:
给你一个长为n的数字序列,要你求出该序列中最长的满足下面要求的序列长度:原序列中存在不重叠的两个序列,这两个序列长度相同且将第一个序列的所有数字加上某个常数可以得到第二个序列.
分析:
本题可见罗穗骞《后缀数组——处理字符串的有力工具》.
首先我们要求的是长度至少为5的这种不重叠序列,其实要求的这两个序列相似等价于他们两个序列中相邻元素的增幅完全一样.所以我们可以用长n的原数组的后一个数减前一个数得到长为n-1的一个新数组.
然后在这个新数组中我们只要找最长的不重叠的重复出现2次的最长连续字串即可.
上面这个问题就可以用后缀数组来解了.我们将上面的问题变成判定问题:是否存在一个长度为k的不重叠子序列?
首先对新的数组求sa和height,然后将n(新数组n-1个数,但是需要加上尾0,变成n个数)个排序后的后缀分成几组,保存每组内的height值>=k.那么如果有长为k的重复不重叠子序列,那么它一定是同一组后缀的两个前缀了.如果有一组的sa最大值与sa最小值之差>=k,那么就存在这样的序列,否则k就不合适.
AC代码:注意细节(n=1的时候测试下)
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=20000+1000;
struct SuffixArray
{
int s[maxn];
int sa[maxn],rank[maxn],height[maxn];
int t1[maxn],t2[maxn],c[maxn];
int n;
void build_sa(int m)
{
int i,*x=t1,*y=t2;
for(i=0;i<m;i++) c[i]=0;
for(i=0;i<n;i++) c[x[i]=s[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(int k=1;k<=n;k<<=1)
{
int 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<m;i++) c[i]=0;
for(i=0;i<n;i++) c[x[y[i]]]++;
for(i=1;i<m;i++) c[i]+=c[i-1];
for(i=n-1;i>=0;i--) sa[--c[x[y[i]]]] = y[i];
swap(x,y);
p=1;x[sa[0]]=0;
for(int i=1;i<n;i++)
x[sa[i]]=y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k]?p-1:p++;
if(p>=n) break;
m=p;
}
}
void build_height()
{
int i,j,k=0;
for(i=0;i<n;i++)rank[sa[i]]=i;
for(i=0;i<n;i++)
{
if(k)k--;
j=sa[rank[i]-1];
while(s[i+k]==s[j+k]) k++;
height[rank[i]]=k;
}
}
}sa;
bool check(int k)
{
int min_v=sa.sa[1],max_v=sa.sa[1];
for(int i=2;i<sa.n;i++)
{
if(sa.height[i]>=k)
{
max_v = max(max_v,sa.sa[i]);
min_v = min(min_v,sa.sa[i]);
if(max_v-min_v>=k)
return true;
}
else
{
max_v=min_v=sa.sa[i];
}
}
return false;
}
int main()
{
int n;
while(scanf("%d",&n)==1&&n)
{
int j,k;
scanf("%d",&j);
for(int i=0;i<n-1;i++)
{
scanf("%d",&k);
sa.s[i]=k-j+100;
j=k;
}
if(n<9){printf("0\n"); continue;}
sa.n=n+1;
sa.s[n-1]=0;
sa.build_sa(200);
sa.build_height();
if(check(4)==false){printf("0\n"); continue;}
int l=1,r=sa.n/2;
while(l<r)
{
int m=l+(r-l+1)/2;
if(check(m))
l=m;
else
r=m-1;
}
printf("%d\n",l+1);
}
}