时间限制:5000ms
单点时限:1000ms
内存限制:256MB
描述
小Hi平时的一大兴趣爱好就是演奏钢琴。我们知道一个音乐旋律被表示为长度为 N 的数构成的数列。小Hi在练习过很多曲子以后发现很多作品自身包含一样的旋律。
旋律可以表示为一段连续的数列,相似的旋律在原数列不可重叠,比如在1 2 3 2 3 2 1 中 2 3 2 出现了一次,2 3 出现了两次,小Hi想知道一段旋律中出现次数至少为两次的旋律最长是多少?
解题方法提示
输入
第一行一个整数 N。1≤N≤100000
接下来有 N 个整数,表示每个音的数字。1≤数字≤1000
输出
一行一个整数,表示答案。
样例输入
8
1 2 3 2 3 2 3 1
样例输出
2
小Ho:这一次的问题该如何解决呢?
小Hi:嗯,这次的问题被称为最长不可重叠重复子串问题。
小Ho:和上次的问题好像啊,但是这一次是不可以重叠的,直接使用上次的算法似乎行不通喔。
小Hi:是的。问题的关键就出在直接用 height 数组不能保证两后缀不重叠,我们得换个思路考虑。
小Ho:可不可以二分答案,转化成判定问题呢?
小Hi:是个好思路,这的确是可行的。我们先二分一个k,表示我们假设串中存在长度为k的不可重叠重复子串。
小Ho:嗯,就是这个意思。
小Hi:存在长度为k的不可重复子串等价于存在两个后缀有长度为k的公共前缀(这里没有要求不重叠)。我们检查 height 数组中有哪些值 ≥ k。并且如果有连续的height值 ≥ k,就把对应的后缀分在同一组。这样就保证了该组中所有后缀两两之间的最长公共前缀都是不小于k的。
我们以样例为例,看一下k=2和k=3的情况。
x i height k=2 k=3
1 8 0
1 2 3 2 3 2 3 1 1 1
2 3 1 6 0
2 3 2 3 1 4 2 >=2
2 3 2 3 2 3 1 2 4 >=2 >=3
3 1 7 0
3 2 3 1 5 1
3 2 3 2 3 1 3 3 >=2 >=3
可以看出,当k=2时,”231”和”23231”的公共前缀大于等于k,”23231”和”2323231”的公共前缀也大于等k,所以这3个排名连续的后缀会被分到一组。同理”3231”和”323231”也会被分到一组。
对于k=3,”23231”和”2323231”分到一组,”3131”和”323231”分到一组。
小Ho:我知道了!
小Hi:对,没错!下面我们要看看能不能找出不重叠的重复子串。对于每一组,我们检查这些后缀对应的sa值(也就是后缀起点在原串中的位置i)。如果max{sa} - min{sa} >= k,那么就说明我们能找出一组不重叠的重复子串。
例如对于k=3,”23231”和”2323231”的sa值是4和2,”3131”和”323231”这一组的sa值是5和3,差值都不满足大于等于3,所以找不出不重叠的。
对于k=2,第一组max{sa}-min{sa}=6-2=4满足大于等于2,所以能找出不重叠的。
因为只用统计两次 所以 不同统计次数
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 101000;
int t1[MAXN],t2[MAXN],c[MAXN];
int n;
bool cmp(int *r,int a,int b,int l)
{
return r[a]==r[b]&&r[a+l]==r[b+l];
}
void da(int str[],int sa[],int ra[],int height[],int n,int m)
{
n++;
int i,j,p,*x=t1,*y=t2;
for(i=0;i<m;i++) c[i]=0;
for(i=0;i<n;i++) c[x[i]=str[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(j=1;j<=n;j<<=1)
{
p=0;
for(i=n-j;i<n;i++) y[p++]=i;
for(i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j;
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(i=1;i<n;i++)
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
if(p>=n) break;
m=p;
}
int k=0;
n--;
for(i=0;i<=n;i++) ra[sa[i]]=i;
for(i=0;i<n;i++)
{
if(k) k--;
j=sa[ra[i]-1];
while(str[i+k]==str[j+k])k++;
height[ra[i]]=k;
}
}
int ra[MAXN],height[MAXN];
int sa[MAXN],num[MAXN];
bool ok(int k)
{
int ans=0;
for(int i=1;i<=n;i++)
{
if(height[i]<k) continue;
int maxx=max(sa[i-1],sa[i]),minn=min(sa[i-1],sa[i]);
int j=i;
while(height[j+1]>=k&&j<=n)
{
j++,maxx=max(maxx,sa[j]),minn=min(minn,sa[j]);
}
if(maxx-minn>=k)
return 1;
}
return 0;
}
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++) scanf("%d",&num[i]);
num[n]=0;
da(num,sa,ra,height,n,1001);
int l=0,r=n;
while(l<=r)
{
int mid=(l+r)>>1;
if(ok(mid))
l=mid+1;
else r=mid-1;
}
printf("%d\n",l-1 );
}
本文介绍一种解决最长不可重叠重复子串问题的方法,通过二分查找结合后缀数组来确定最长重复子串的长度,并确保重复子串不重叠。


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



