/*
Author: lcy
Time: 2017-10-6
Hihocoder1407 后缀数组
求字符串中至少重复1次且不重叠的最长长度
与之前的题目一样,只不过这次加上了“不重叠”这个条件
最长长度满足二分答案的条件,即如果不存在长度a的连续序列满足
条件,那么长度大于a的连续序列更不可能,另一方面,可能存在长度
大于a的连续序列满足条件。
重叠部分一定出现在一段连续的LCP中,所以判断时,我们选择一段尽量长的
LCP区间,使得其中所有值都大于待检测长度,同时维护最大与最小的后缀起始点
位置,使得他们的差值大于待检测长度,这样就不会重叠。如果能找到这样一段区间,
那么意味着待检测长度可以满足条件。
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>
#include <map>
#include <algorithm>
using namespace std;
#define ll long long
#define fr(i,a,b) for(int i=a;i<=b;i++)
#define frr(i,a,b) for(int i=a;i>=b;i--)
#define ms(a,b) memset(a,b,sizeof(a))
#define scfd(a) scanf("%d",a)
#define scflf(a) scanf("%lf",a)
#define scfs(a) scanf("%s",a)
#define ptfd(a) printf("%d\n",a)
#define ptfs(a) printf("%s\n",a)
#define showd(a,b) printf(a"=%d\n",b)
#define showlf(a,b) printf(a"=%lf\n",b)
#define shows(a,b) printf(a"=%s\n",b)
#define mmcp(a,b) memcpy(a,b,sizeof(b))
#define pb(a) push_back(a)
#define L 0
#define S 1
const int MAXN=100005;
inline bool same(int *s,int *tp,int l,int a,int b){
while(a<=l&&b<=l){
if(s[a++]!=s[b++])
return false;
if(tp[a]==S&&tp[a-1]==L)
break;
if(tp[b]==S&&tp[b-1]==L)
break;
}
return s[a]==s[b];
}
//s数组字符编码从1开始,0等价于'#'
static int *sais(int *s,int l){
//s数组第l位为0(#)
s[l]=0;
int SIGMA=*max_element(s,s+l+1)+13;
int *cnt=new int[SIGMA];
int *lp=new int[SIGMA];
int *sp=new int[SIGMA];
int *sa=new int[l+13];
int *s1=new int[l+13];
int *tp=new int[l+13];
int *name=new int[l+13];
int *pos=new int[l+13];
tp[l]=S;//末尾最小
//标记LS
frr(i,l-1,0)
if(s[i]>s[i+1])tp[i]=L;
else if(s[i]<s[i+1])tp[i]=S;
else tp[i]=tp[i+1];
//求LSM数组
fill(cnt,cnt+SIGMA,0);
fr(i,0,l)cnt[s[i]]++;
fr(i,1,SIGMA-1)cnt[i]+=cnt[i-1];//字符数量的前缀和
lp[0]=0;
fr(i,1,SIGMA-1)lp[i]=cnt[i-1];//求每个字符桶L型的起始位置
fr(i,0,SIGMA-1)sp[i]=cnt[i]-1;//求每个字符桶S型的起始位置
fill(sa,sa+l+1,-1);//注意在结尾添加了#,长度为l+1
fr(i,1,l)if(tp[i]==S&&tp[i-1]==L)sa[sp[s[i]]--]=i;//任意顺序先放*型字符
fr(i,0,l)if(sa[i]>0&&tp[sa[i]-1]==L)sa[lp[s[sa[i]-1]]++]=sa[i]-1;//诱导L型后缀
fr(i,0,SIGMA-1)sp[i]=cnt[i]-1;//还原S型起始位置
frr(i,l,0)if(sa[i]>0&&tp[sa[i]-1]==S)sa[sp[s[sa[i]-1]]--]=sa[i]-1;//诱导S型后缀
fill(name,name+l+1,-1);
int cur=0,last=-1,ed=0;
bool is_same=false;
fr(i,0,l)
if(sa[i]>0&&tp[sa[i]]==S&&tp[sa[i]-1]==L){
if(last!=-1){
if(!same(s,tp,l,sa[i],last))cur++;
else is_same=true;
}
name[sa[i]]=cur;
last=sa[i];
}
fr(i,0,l)if(name[i]>=0){pos[ed]=i,s1[ed]=name[i];ed++;}//pos[i]表示s1[i]在s中对应的下标
lp[0]=0;
fr(i,1,SIGMA-1)lp[i]=cnt[i-1];//求每个字符桶L型的起始位置
fr(i,0,SIGMA-1)sp[i]=cnt[i]-1;//求每个字符桶S型的起始位置
fill(sa,sa+l+1,-1);//注意在结尾添加了#,长度为l+1
//求s1的后缀数组sa1
int *sa1;
if(is_same)//如果s1字符串中有相同元素,那么需要递归求sa1
sa1=sais(s1,ed-1);//s1[ed]一定为0(#排最小),所以s1递归下去长度只有ed-1(最后1位不算)
else{//如果s1字符串中每个元素不同,直接桶排序生成sa1即可
sa1=new int[ed+3];
fill(sa1,sa1+ed+3,-1);
fr(i,0,ed-1)sa1[s1[i]]=i;
}
//sa1诱导sa
frr(i,ed-1,0)sa[sp[s[pos[sa1[i]]]]--]=pos[sa1[i]];//逆向添加*型,因为桶中的S的下标是倒着移动的
fr(i,0,l)if(sa[i]>0&&tp[sa[i]-1]==L)sa[lp[s[sa[i]-1]]++]=sa[i]-1;//诱导L型后缀
fr(i,0,SIGMA-1)sp[i]=cnt[i]-1;//还原S型起始位置
frr(i,l,0)if(sa[i]>0&&tp[sa[i]-1]==S)sa[sp[s[sa[i]-1]]--]=sa[i]-1;//诱导S型后缀
delete[] cnt;
delete[] lp;
delete[] sp;
delete[] s1;
delete[] sa1;
delete[] pos;
delete[] name;
delete[] tp;
return sa;
}
void getheight(int *s,int *sa,int *rk,int *hei,int l){
//hei[i]:suf[sa[i]...l]与suf[sa[i-1]...l]的最长前缀,即排名第i的与排名第i+1的最长前缀
//h[i] = hei[rank[i]] 即suf[i...l]与suf[p...l]的最长前缀,其中suf[p...l]排在suf[i...1]之前1位
//有 h[i] >= h[i-1] - 1
fr(i,0,l)rk[sa[i]]=i;
int k=0;
fr(i,0,l){
if(k)k--;
while(s[i+k]==s[sa[rk[i]-1]+k])k++;//sa[rk[i]-1]代表后缀数组中排在suf[i...l]前1位的后缀起始点
hei[rk[i]]=k;
}
}
int n;
int s[MAXN],rk[MAXN],hei[MAXN];
bool ok(int x,int *sa){
int now=1,st=-1,mx=-1,mn=MAXN+100;
while(now<=n){
if(hei[now]>=x){
if(st==-1)st=now;
mx=max(mx,sa[now]);
mx=max(mx,sa[now-1]);
mn=min(mn,sa[now-1]);
mn=min(mn,sa[now]);
}
else{
if(st>=0){
if(mx-mn>=x)return true;
mx=-1,mn=MAXN+100,st=-1;
}
}
now++;
}
return false;
}
int main(){
scfd(&n);
fr(i,0,n-1)scfd(s+i);
int l=0,r=n+1;
int *sa=sais(s,n);
getheight(s,sa,rk,hei,n);
while(l<r-1){
int mid=(l+r)/2;
if(ok(mid,sa))
l=mid;
else
r=mid;
}
printf("%d\n",l);
return 0;
}