题目描述
给定一个长为n的字符串,求其不可重叠的最长重复子串长度(即存在两个这样的子串并且不相交)
输入格式
仅一行,为题目描述的字符串
输出格式
输出一行一个整数,表示不可重叠的最长重复子串的长度
数据范围
对于100%的数据,保证1≤n≤100000,串的所有字符均为小写字母
题解
子串是后缀的前缀
所以两个后缀的lcp就是重复子串
如果有满足条件的长度为len的重复子串,那么就有len-1的重复子串
具有二分性
将sa数组按下标列出,height[i]为sa[i]和sa[i-1]的桥梁
check函数中,将长度<mid的桥梁删去,分成了一些组,这些组内的任意两个后缀的lcp≤mid,因为两个后缀的lcp就是他们之前所有桥梁的min
那么在组内如果找到两个后缀,他们起始位置只差≥mid,那么就可以
求后缀数组时,c数组只开到了字符集大小,在luogu模板过了可还行
#include<bits/stdc++.h> using namespace std; const int maxn=1000005; int n,m=26; char s[maxn]; int x[maxn],y[maxn],c[maxn]; int sa[maxn],rk[maxn],height[maxn]; void get_sa(){ for(int i=0;i<n;i++) c[x[i]=s[i]-'a']++; for(int i=1;i<n;i++) c[i]+=c[i-1]; for(int i=n-1;i>=0;i--) sa[--c[x[i]]]=i; for(int k=1;k<=n;k<<=1){ int p=0; for(int i=n-k;i<n;i++) y[p++]=i; for(int i=0;i<n;i++) if(sa[i]>=k) y[p++]=sa[i]-k; for(int i=0;i<m;i++) c[i]=0; for(int i=0;i<n;i++) c[x[i]]++; for(int i=1;i<n;i++) c[i]+=c[i-1]; for(int 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]]&&sa[i]+k<n&&sa[i-1]+k<n&&y[sa[i]+k]==y[sa[i-1]+k] ? p-1 : p++; if(p==n) break; m=p; } } void get_h(){ for(int i=0;i<n;i++) rk[sa[i]]=i; int k=0; for(int i=0;i<n;i++){ if(!rk[i]) continue; if(k) k--; int j=sa[rk[i]-1]; while(j+k<n&&i+k<n&&s[i+k]==s[j+k]) k++; height[rk[i]]=k; } } bool check(int x){ int mx,mi; mx=mi=sa[0]; for(int i=1;i<n;i++){ if(height[i]>=x){ mx=max(mx,sa[i]); mi=min(mi,sa[i]); } else mx=mi=sa[i]; if(mx-mi>=x) return true; } return false; } int main(){ freopen("longest.in","r",stdin); freopen("longest.out","w",stdout); scanf("%s",s); n=strlen(s); get_sa(); get_h(); int l=0,r=n,ans=0; while(l<=r){ int mid=(l+r)>>1; if(check(mid)) ans=mid,l=mid+1; else r=mid-1; } printf("%d",ans); }