/*
Author: lcy
Time: 2017-10-5
Hihocoder1403 后缀数组
求字符串中至少重复k次最长的长度
既然重复,那么假如把后缀排好序,重复的片段一定在一个区间内
一段连续LCP区间内的最小值即为该区间内最长的公共前缀
现在已知了LCP,问题转化为求长度至少为k的连续区间内LCP的最小值的最大值
因为增加区间长度,并不会使该区间的最小值更大,即不会使得结果更优,所以只要考虑区间长度为k即可
用单调队列维护
*/
#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=20005;
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;
}
}
struct mono_queue{
int q[MAXN],st,ed;
mono_queue(){
st=ed=0;
ms(q,0);
}
void push(int x){
while(ed>st&&q[ed-1]>x)ed--;
q[ed++]=x;
}
void pop(int x){
if(x==q[st])st++;
}
int get(){
return q[st];
}
}mq;
int n,k;
int s[MAXN];
int rk[MAXN],hei[MAXN];
int main(){
scanf("%d%d",&n,&k);
fr(i,0,n-1)scfd(s+i);
int *sa=sais(s,n);
getheight(s,sa,rk,hei,n);
fr(i,0,k-2)mq.push(hei[i]);
int ans=mq.get();
fr(i,k-1,n){
mq.pop(hei[i-k+1]);
mq.push(hei[i]);
ans=max(ans,mq.get());
}
printf("%d\n",ans);
return 0;
}