描述
小Hi平时的一大兴趣爱好就是演奏钢琴。我们知道一个音乐旋律被表示为长度为 N 的数构成的数列。小Hi在练习过很多曲子以后发现很多作品自身包含一样的旋律。
旋律可以表示为一段连续的数列,相似的旋律在原数列不可重叠,比如在1 2 3 2 3 2 1 中 2 3 2 出现了一次,2 3 出现了两次,小Hi想知道一段旋律中出现次数至少为两次的旋律最长是多少?
解题方法提示
小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,所以能找出不重叠的。
我们给出如下c++代码:
bool check(int K){ for(int i=1;i<=n;i++) if(height[i]< K) { minsa=sa[i]; maxsa=sa[i]; } else { minsa=min(minsa,sa[i]); maxsa=max(maxsa,sa[i]); if(maxsa-minsa>=K)return true; } return false;}
小Ho:哈哈,不难嘛,我马上去实现一发!
输入
第一行一个整数 N。1≤N≤100000
接下来有 N 个整数,表示每个音的数字。1≤数字≤1000
输出
一行一个整数,表示答案。
-
样例输入
-
81 2 3 2 3 2 3 1
样例输出
-
2
<span style="font-family:Arial;font-size:14px;">#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <climits>
using namespace std;
const int MAXN = 100010;
int ch[MAXN], sa[MAXN], tsa[MAXN], cntA[MAXN], cntB[MAXN], Rank[MAXN], A[MAXN], B[MAXN], height[MAXN];
int n;
void solve()
{
for( int i=0 ; i<1001 ; i++ )
cntA[i] = 0;
for( int i=1 ; i<=n ; i++ )
cntA[ch[i]]++;
for( int i=1 ; i<=1000 ; i++ )
cntA[i] += cntA[i-1];
for( int i=n ; i; i-- )
sa[cntA[ch[i]]--] = i;
Rank[sa[1]] = 1;
for( int i=2 ; i<=n ; i++ )
{
Rank[sa[i]] = Rank[sa[i-1]];
if( ch[sa[i]] != ch[sa[i-1]] )
Rank[sa[i]]++;
}
for( int l=1 ; l<=n ; l<<=1 )
{
for( int i=0 ; i<=n ; i++ )
cntA[i] = cntB[i] = 0;
for( int i=1 ; i<=n ; i++ )
{
cntA[A[i] = Rank[i]]++;
cntB[B[i] = (i+l<=n) ? Rank[i+l] : 0 ]++;
}
for( int i=1 ; i<=n ; i++ )
cntB[i] += cntB[i-1];
for( int i=n ; i ; i-- )
tsa[cntB[B[i]]--] = i;
for( int i=1 ; i<=n ; i++ )
cntA[i] += cntA[i-1];
for( int i=n ; i ; i-- )
sa[cntA[A[tsa[i]]]--] = tsa[i];
Rank[sa[1]] = 1;
for( int i=2 ; i<=n ; i++ )
{
Rank[sa[i]] = Rank[sa[i-1]];
if( A[sa[i]] != A[sa[i-1]] || B[sa[i]] != B[sa[i-1]] )
Rank[sa[i]]++;
}
}
for( int i=1, j=0 ; i<=n ; i++ )
{
if(j) j--;
while( ch[i+j] == ch[sa[Rank[i]-1] + j ] )
j++;
height[Rank[i]] = j;
}
}
bool check( int k )
{
int minsa,maxsa;
minsa = maxsa = 0;
for( int i=1 ; i<=n ; i++ )
{
if( height[i] < k )
{
minsa = sa[i];
maxsa = sa[i];
}
else
{
minsa = min( minsa, sa[i] );
maxsa = max( maxsa, sa[i] );
if( maxsa-minsa >= k )
return true;
}
}
return false;
}
int main()
{
//freopen( "in.txt", "r", stdin );
cin>>n;
for( int i=1 ; i<=n ; i++ )
cin>>ch[i];
solve();
int ans = 0;
for( ; ans<= n/2; ans++ )
{
if( check(ans) && !check(ans+1) )
break;
}
cout<<ans<<endl;
//cout << "Hello world!" << endl;
return 0;
}
</span>