这个题和昨天写的Musical Theme基本一样,那篇博客有后缀数组的学习,如果那个题没有写过的话,搞完了这个题可以去做一下。
题意
显然就是求最长可重复子串的长度,但是该子串重复的次数需不小于K。
思路
罗穗骞 《后缀数组——处理字符串的有力工具》
先求后缀树组,而后利用后缀树组求height数组(height[i]:存储排名为i和i-1的后缀子串的最长重复前缀子串长度 )。把排序后的后缀分成若干组,其中每组的后缀之间的height值都不小于k。例如,字符串为“aabaaaab”,当k=2时,后缀分成了4组如图所示
可以想到当一组内的height都不小于k时,我们只需判断组内包含的后缀数组的数量是否不小于K个就行了,如果满足,那么当长度等于k时也是满足条件的,因此我们需要对长度进行二分,然后判断是否可行即可
代码
#pragma GCC optimize(2)
//#include<bits/stdc++.h>
#include<iostream>
#include<algorithm>
#include<cstdio>
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=2e4+7;
const ll maxnum=1e6+7;
ll N,M,K;
ll sa[maxn],rank[maxn],r[maxn],height[maxn],tmp[maxn];
bool cmp(ll i,ll j){
if(rank[i]!=rank[j])
return rank[i]<rank[j];
ll r1=i+K<=N?rank[i+K]:-1;
ll r2=j+K<=N?rank[j+K]:-1;
return r1<r2;
}
//求后缀数组
void do_sa(){
ll i,j;
for(i=0;i<=N;i++){
sa[i]=i;
rank[sa[i]]=(i!=N?r[i]:-1);
}
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);
for(i=0;i<=N;i++)
rank[i]=tmp[i];
}
return ;
}
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;//height[i]:存储排名为i和i-1的后缀子串的最长前缀子串长度
}
return ;
}
bool check(ll mid){
ll i,j,cnt=0;
for(i=1;i<=N;i++){
if(height[i]>=mid){
cnt++;
if(cnt+1>=M)
return true;
}
else
cnt=0;
}
return false;
}
int main()
{
// freopen(".../.txt","w",stdout);
// freopen(".../.txt","r",stdin);
ios::sync_with_stdio(false);
ll i,j,k;
cin>>N>>M;
for(i=0;i<N;i++)
cin>>r[i];
do_sa();
get_height();
ll L=0,R=N,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;
}
}
cout<<res<<endl;
return 0;
}