CodeEff/ECCO huggingface dataset:
# Original solution
from collections import defaultdict
import sys
input = sys.stdin.readline
exit = sys.exit


def main():
    N = int(input().strip())
    S = input().strip()
    half_N = N//2
    cand = []
    flag = True
    cappend = cand.append
    for i in range(N):
        tmp = []
        tappend = tmp.append
        for j in range(1, N-i-1):
            if S[i] == S[i+j]:
                flag = False
                tappend(i+j)
        cappend(tmp)
    if flag:
        print((0))
        exit()

    for length in range(1, N//2+1):
        flag = True
        new_cand = []
        ncappend = new_cand.append
        for i in range(N-length):
            tmp = []
            now_check = S[i+length]
            tappend = tmp.append
            for c in cand[i]:
                if i + length >= c or c + length >= N:
                    continue
                if S[c+length] == now_check:
                    flag = False
                    tappend(c)
            ncappend(tmp)
        cand = new_cand
        if flag:
            print(length)
            exit()

    print((length+1))

if __name__ == "__main__":
    main()


# Optimized solution
import sys
input = sys.stdin.readline


def main():
    N = int(eval(input()))
    S = input().strip()

    ans = 0
    for k in range(N):
        now_S = S[k:]
        ans_length = [-1] * (len(now_S)+1)
        j = -1
        for i, c in enumerate(now_S):
            left_end = now_S[j]
            right_end = c
            while j >= 0 and left_end != right_end:
                j = ans_length[j]
                left_end = now_S[j]
            j += 1
            ans_length[i+1] = j

        for i, length in enumerate(ans_length):
            tmp_max_length = i // 2
            if length <= tmp_max_length:
                ans = max(ans, length)
    print(ans)


if __name__ == "__main__":
    main()
# Original solution
# 2019-11-18 17:34:23(JST)
import sys
# import collections
# import math
# from string import ascii_lowercase, ascii_uppercase, digits
# from bisect import bisect_left as bi_l, bisect_right as bi_r
# import itertools
# from functools import reduce
# import operator as op
# import re
# import heapq
# import array
# from scipy.misc import comb # (default: exact=False)
# import numpy as np 



def main():
    n, s = sys.stdin.read().split()
    n = int(n)
    
    # a = [0 for _ in range(n)]
    # i = j = 0
    # while i < n:
    #     while i+j < n and s[j] == s[i+j]: 
    #         j += 1
    #     a[i] = j
    #     if j == 0:
    #         i += 1 
    #         continue
    #     k = 1
    #     while i+k < n and k+a[k] < j:
    #         a[i+k] = a[k]
    #         k += 1
    #     i += k
    #     j -= k

    # print(max(l for l in a if l <= n // 2))
    # print(a)
    
    ma = 0
    for k in range(n):
        sub = s[k:]
        a = [0 for _ in range(len(sub))]
        i, j = 1, 0
        while i < len(sub):
            while i+j < len(sub) and sub[j] == sub[i+j]:
                j += 1
                if j == len(sub) // 2:
                    break
            a[i] = j
            if j == 0:
                i += 1
                continue
            k = 1
            while i+k < len(sub) and k+a[k] < j:
                a[i+k] = a[k]
                k += 1
            i += k
            j -= k
        ma = max(ma, max(a))
    print(ma)

    # a = [0 for _ in range(n)]
    # a[0] = n 
    # c = 0
    # for i in range(1, n):
    #     if i+a[i-c] < c+a[c]:
    #         a[i] = a[i-c]
    #     else:
    #         j = max(0, c+a[c]-i)
    #         while i+j < n and s[s] == s[i+j]:
    #             j += 1
    #         a[i] = j
    
    # print(a)

    # zz = [[0 for _ in range(n)] for _ in range(n)]

    # ans = [0 for _ in range(n)]
    # for k in range(n):
    #     # z = zz[k]
    #     z = [0 for _ in range(n)]
    #     sub = s[k:]
    #     for i in range(1, len(sub)):
    #         count = 0
    #         for j in range(len(sub)-i):
    #             if sub[j] == sub[i+j]:
    #                 count += 1
    #                 if count == n // 2:
    #                     break
    #             else:
    #                 break
    #         z[i] = count
    #     ans[k] = max(z)
    #     # zz[k] = z
    # print(max(ans))






if __name__ == "__main__":
    main()


# Optimized solution
# 2019-11-18 17:34:23(JST)
import sys
# import collections
# import math
# from string import ascii_lowercase, ascii_uppercase, digits
# from bisect import bisect_left as bi_l, bisect_right as bi_r
# import itertools
# from functools import reduce
# import operator as op
# import re
# import heapq
# import array
# from scipy.misc import comb # (default: exact=False)
# import numpy as np 


def main():
    n, s = sys.stdin.read().split()
    n = int(n)

    ans = 0
    i, j = 0, 1
    while j < n:
        t = s[i:j]
        if t in s[j:]:
            ans = max(ans, j - i)
            j += 1
        else:
            i += 1
    print(ans)
    
if __name__ == "__main__":
    main()
# Original solution
def solve():
    N = int(eval(input()))
    Ss = input().rstrip()

    for L in reversed(list(range(1, N//2+1))):
        setCand = set()
        RHs = []
        for i in range(N-L+1):
            if i <= N-2*L or L <= i:
                RH = hash(Ss[i:i+L])
            if i <= N-2*L:
                RHs.append(RH)
            if L <= i:
                setCand.add(RHs[i-L])
                if RH in setCand:
                    print(L)
                    break
        else:
            continue
        break
    else:
        print((0))


solve()


# Optimized solution
def solve():
    N = int(eval(input()))
    Ss = input().rstrip()

    def isOK(L):
        setCand = set()
        RHs = []
        for i in range(N-L+1):
            RH = hash(Ss[i:i+L])
            RHs.append(RH)
            if i-L >= 0:
                setCand.add(RHs[i-L])
            if RH in setCand:
                return True
        return False

    ng, ok = N//2+1, 0
    while abs(ok-ng) > 1:
        mid = (ng+ok) // 2
        if isOK(mid):
            ok = mid
        else:
            ng = mid

    print(ok)


solve()
# Original solution
#!/usr/bin/env python3
def Z(S):
    n = len(S)
    LCP = [0]*n
    i = 1
    j = c = 0
    # c : 最も末尾側までLCPを求めたインデックス
    for i in range(1, n):
        # i番目からのLCPが以前計算したcからのLCPに含まれている場合
        if i+LCP[i-c] < c+LCP[c]:
            LCP[i] = LCP[i-c]
        else:
            j = max(0, c+LCP[c]-i)
            while i+j < n and S[j] == S[i+j]: j+=1
            LCP[i] = j
            c = i
    LCP[0] = n
    return LCP

a = 0
n, s = open(0)
for i in range(int(n)):
    for j, l in enumerate(Z(s[i:])):
        a = max(a, min(l, j))
print(a)

# Optimized solution
def Z(S):
    n = len(S)
    LCP = [0]*n
    i = 1
    j = c = 0
    # c : 最も末尾側までLCPを求めたインデックス
    for i in range(1, n):
        # i番目からのLCPが以前計算したcからのLCPに含まれている場合
        if i+LCP[i-c] < c+LCP[c]:
            LCP[i] = LCP[i-c]
        else:
            j = max(0, c+LCP[c]-i)
            while i+j < n and S[j] == S[i+j]: j+=1
            LCP[i] = j
            c = i
    LCP[0] = n
    return LCP

a = 0
n, s = open(0)
for i in range(int(n)):
    for j, l in enumerate(Z(s[i:])):
        a = max(a, min(l, j))
print(a)
# Original solution
import sys
n = int(sys.stdin.readline())
S = sys.stdin.readline().rstrip()


dp = [[0 for i in range(n)] for j in range(n)]
#初期条件
for j in range(n):
    if S[0] == S[j]:
        dp[0][j] = 1
    else:
        dp[0][j] = 0

#print(dp)

for i in range(n):
    for j in range(n):
        if S[i] ==S[j]:
            dp[i][j] = min(dp[i-1][j-1] +1, n//2)
        else:
            dp[i][j] = 0

maxp = 0
for i in range(n):
    for j in range(n):
        if maxp < dp[i][j] and i !=j:
            maxp = dp[i][j]
#print(dp)
print(maxp)

# Optimized solution
import sys
n = int(sys.stdin.readline())
S = sys.stdin.readline().rstrip()


dp = [[0 for i in range(n)] for j in range(n)]
#初期条件
for j in range(1, n):
    if S[0] == S[j]:
        dp[0][j] = 1

#print(dp)

for i in range(1, n-1):
    for j in range(i+1, n):
        if S[i] ==S[j]:
            dp[i][j] = min(dp[i-1][j-1] +1, j-i)

maxp = 0
for i in range(n-1):
    for j in range(i+1, n):
        if maxp < dp[i][j]:
            maxp = dp[i][j]
#print(dp)
print(maxp)
# Original solution
import sys, math
input = sys.stdin.readline
rs = lambda: input().strip()
ri = lambda: int(eval(input()))
rl = lambda: list(map(int, input().split()))
mod = 10**9 + 7

N = ri()
S = rs()

def get_max(i):
	ok, ng = i, len(S)
	while ng-ok > 1:
		c = (ok+ng)//2
		if S[i:c] in S[c:]:
			ok = c
		else:
			ng = c
	return ok-i

ans = 0
for i in range(N):
	l = get_max(i)
	ans = max(ans, l)
print(ans)


# Optimized solution
import sys, math
input = sys.stdin.readline
rs = lambda: input().strip()
ri = lambda: int(eval(input()))
rl = lambda: list(map(int, input().split()))
mod = 10**9 + 7

N = ri()
S = rs()

dp = [[0] * (N+1) for _ in range(N+1)]

ans = 0
for i in range(N):
	for j in range(i+1,N):
		if S[i] == S[j]:
			if i+1 < j+1-(dp[i][j]+1)+1:
				dp[i+1][j+1] = dp[i][j]+1
			else:
				dp[i+1][j+1] = 0
			ans = max(ans, dp[i+1][j+1])
print(ans)
# Original solution
import sys

sr = lambda: sys.stdin.readline().rstrip()
ir = lambda: int(sr())
lr = lambda: list(map(int, sr().split()))

#二分探索
N = ir()
S = sr()

def check(x):
    # x 文字
    bl = False
    for i in range(N):
        for j in range(i+x, N):
            if S[i:i+x] == S[j:j+x]:
                bl = True
    return bl

ok = 0; ng = N
while abs(ng-ok) > 1:
    mid = (ok+ng) // 2
    if check(mid):
        ok = mid
    else:
        ng = mid

print(ok)


# Optimized solution
import sys

sr = lambda: sys.stdin.readline().rstrip()
ir = lambda: int(sr())
lr = lambda: list(map(int, sr().split()))

#二分探索
N = ir()
S = sr()

def check(x):
    # x文字右側にずらした場所を調べる
    candidate = set()
    for i in range(N - 2*x + 1):
        candidate.add(S[i:i+x])
        if S[i+x:i+2*x] in candidate:
            return True
    return False

ok = 0; ng = N
while abs(ng-ok) > 1:
    mid = (ok+ng) // 2
    if check(mid):
        ok = mid
    else:
        ng = mid

print(ok)
# Original solution
import sys
input = sys.stdin.readline

n = int(eval(input()))
s = input().rstrip()

strings = [[] for _ in range(n // 2 + 1)]

for i in range(n):
    string = [s[i]]
    strings[1].append(s[i])
    for j in range(i + 1, n):
        string.append(s[j])
        if j - i + 1 < 1 + n // 2:
            strings[j - i + 1].append("".join(string))

ans = 0

for i in range(n // 2, 0, -1):
    check = strings[i]
    for j in range(len(check)):
        check_set = set(check[j + i:])
        if check[j] in check_set:
            print(i)
            sys.exit()

print(ans)


# Optimized solution
import sys
input = sys.stdin.readline

n = int(eval(input()))
s = input().rstrip()

def z_algo(S):
    N = len(S)
    A = [0]*N
    i = 1; j = 0
    A[0] = l = len(S)
    while i < l:
        while i+j < l and S[j] == S[i+j]:
            j += 1
        if not j:
            i += 1
            continue
        A[i] = j
        k = 1
        while l-i > k < j - A[k]:
            A[i+k] = A[k]
            k += 1
        i += k; j -= k
    return A

ans = 0

for i in range(n):
    s_ = s[i:]
    z_s = z_algo(s_)
    for j in range(len(z_s)):
        ans = max(ans, min(j, z_s[j]))

print(ans)
# Original solution
# -*- coding: utf-8 -*-
import sys
N=int(sys.stdin.readline().strip())
S=sys.stdin.readline().strip()
ans=0   #文字列一致が存在しない場合は0を返す

for i in range(N):
    s=S[i]
    for j in range(i+1,N):
        while j<N:  #jが文字列からはみ出さない間はループを回す
            t=S[j]
            if s==t:    #文字が一致したら、jを1つ進める
                current_j=j     #文字が一致したら現在のjの位置を記録しておく
                cnt=0
                while s==t:
                    cnt+=1
                    i+=1    #チェックする文字を1文字ずつ右にスライド
                    j+=1    #チェックする文字を1文字ずつ右にスライド
                    if i==current_j:    #iがjのスタート位置と重なったらbreak
                        break
                    elif i<N and j<N:    #i,jともに文字列内だったら
                        s=S[i]
                        t=S[j]
                    else:    #i or jが文字列の右側からはみ出したら
                        break
                ans=max(ans,cnt)
                j=current_j+1 #元のjの値を戻して、1つ進める
            else:    #文字が一致しなかったら、jを1つ進める
                j+=1

print(ans)

# Optimized solution
# -*- coding: utf-8 -*-
import sys
N=int(sys.stdin.readline().strip())
S=sys.stdin.readline().strip()

ans=0
for d in range(1,N):
    cnt=0
    for s,t in zip(S[d:N],S[:N-d]):
        if s==t:
            cnt+=1
        else:
            ans=max(ans,min(d,cnt)) #最大の文字列一致はd以下
            cnt=0
    else:
        if 0<cnt:   #最後の文字が一致していた場合
            ans=max(ans,min(d,cnt))    #残ったcntも答えの候補になる
print(ans)
# Original solution
from heapq import heappush, heappop
from collections import deque,defaultdict,Counter
import itertools
from itertools import permutations,combinations
import sys
import bisect
import string
import math
import time
import random
def I():
    return int(input())
def MI():
    return map(int,input().split())
def LI():
    return [int(i) for i in input().split()]
def LI_():
    return [int(i)-1 for i in input().split()]
def show(*inp,end='\n'):
    if show_flg:
        print(*inp,end=end)
YN=['Yes','No']
mo=10**9+7
ts=time.time()
sys.setrecursionlimit(10**6)
#input=sys.stdin.readline
show_flg=False
#show_flg=True

ans=0
an=[]
n=I()
st=-1
s=list(input())
can=[]
for d in range(1,n):
    cnt=0
    for i in range(n-d):
        if s[i]==s[i+d] and st+cnt+1<=st+d:
            if st==-1:
                st=i
            #show('d,i,s[i],st,cnt,an',d,i,s[i],st,cnt,an)
            cnt+=1
            an+=s[i]
            ans=max(ans,cnt)
        else:
            #show('d,i,s[i],st,cnt,an',d,i,s[i],st,cnt,an,'els')
            cnt=0
            can+=[an]
            an=[]
            st=-1
    cnt=0
print(ans)
show(can)


# Optimized solution
from heapq import heappush, heappop
from collections import deque,defaultdict,Counter
import itertools
from itertools import permutations,combinations
import sys
import bisect
import string
import math
import time
import random
def I():
    return int(input())
def MI():
    return map(int,input().split())
def LI():
    return [int(i) for i in input().split()]
def LI_():
    return [int(i)-1 for i in input().split()]
def show(*inp,end='\n'):
    if show_flg:
        print(*inp,end=end)
YN=['Yes','No']
mo=10**9+7
ts=time.time()
sys.setrecursionlimit(10**6)
#input=sys.stdin.readline
show_flg=False
#show_flg=True

ans=0
an=[]
n=I()
st=-1
s=list(input())
can=[]
for d in range(1,n):
    cnt=0
    for i in range(n-d):
        if s[i]==s[i+d] and st+cnt+1<=st+d:
            if st==-1:
                st=i
            #show('d,i,s[i],st,cnt,an',d,i,s[i],st,cnt,an)
            cnt+=1
            #an+=s[i]
            ans=max(ans,cnt)
        else:
            #show('d,i,s[i],st,cnt,an',d,i,s[i],st,cnt,an,'els')
            cnt=0
            #can+=[an]
            #an=[]
            st=-1
    cnt=0
print(ans)
#show(can)
# Original solution
import sys
import copy

sys.setrecursionlimit(10 ** 6)
int1 = lambda x: int(x) - 1
p2D = lambda x: print(*x, sep="\n")
def IS(): return sys.stdin.readline()[:-1]
def II(): return int(sys.stdin.readline())
def MI(): return map(int, sys.stdin.readline().split())
def LI(): return list(map(int, sys.stdin.readline().split()))
def LI1(): return list(map(int1, sys.stdin.readline().split()))
def LII(rows_number): return [II() for _ in range(rows_number)]
def LLI(rows_number): return [LI() for _ in range(rows_number)]
def LLI1(rows_number): return [LI1() for _ in range(rows_number)]

def main():
	N = II()
	SS = IS()
	ans = 0
	for l in range(N-1):
		i = 1
		j = 0
		S = copy.copy(SS[l:])
		A = [0]*len(S)
		while (i < len(S)):
			while (i+j < len(S) and S[j] == S[i+j] and j < i):
				j+=1
			A[i] = j
			if (j == 0):
				i+=1
				continue
			i += 1
			j = 0
		ans = max(max(A),ans)
	print(ans)

main()


# Optimized solution
import sys
import copy

sys.setrecursionlimit(10 ** 6)
int1 = lambda x: int(x) - 1
p2D = lambda x: print(*x, sep="\n")
def IS(): return sys.stdin.readline()[:-1]
def II(): return int(sys.stdin.readline())
def MI(): return map(int, sys.stdin.readline().split())
def LI(): return list(map(int, sys.stdin.readline().split()))
def LI1(): return list(map(int1, sys.stdin.readline().split()))
def LII(rows_number): return [II() for _ in range(rows_number)]
def LLI(rows_number): return [LI() for _ in range(rows_number)]
def LLI1(rows_number): return [LI1() for _ in range(rows_number)]

def main():
	N = II()
	SS = IS()
	ans = 0
	for l in range(N-1):
		i = 1
		j = 0
		S = copy.copy(SS[l:])
		A = [0]*len(S)
		while (i < len(S)):
			while (i+j < len(S) and S[j] == S[i+j]):
				j+=1
			A[i] = j
			if (j == 0):
				i+=1
				continue
			k = 1
			while (i+k < len(S) and k+A[k] < j):
				A[i+k] = A[k]
				k+=1
			i += k
			j -= k
		for m in range(len(S)):
			ans = max(ans, min(m, A[m]))
	print(ans)

main()
# Original solution
import sys
stdin = sys.stdin

sys.setrecursionlimit(10 ** 7)

def li(): return list(map(int, stdin.readline().split()))
def li_(): return [int(x) - 1 for x in stdin.readline().split()]
def lf(): return list(map(float, stdin.readline().split()))
def ls(): return stdin.readline().split()
def ns(): return stdin.readline().rstrip()
def lc(): return list(ns())
def ni(): return int(stdin.readline())
def nf(): return float(stdin.readline())

n = ni()
s = lc()

dic = {}
strs = set()

slice = [[""]*n for _ in range(n)]
for st in range(n):
    curstr = s[st]
    for ed in range(st+1, min(st+n//2+2, n)):
        curstr += s[ed]
        slice[st][ed] = curstr

for st in range(n):
    for ed in range(st+1, min(st+n//2+2, n)):
        stri = slice[st][ed]
        if not stri in strs:
            dic[stri] = [st, -1]
            strs.add(stri)
        else:
            dic[stri][1] = st

ans = 0
for key, val in list(dic.items()):
    if val[1] - val[0] >= len(key):
        ans = max(ans, len(key))

print(ans)

# Optimized solution
import sys
stdin = sys.stdin

sys.setrecursionlimit(10 ** 7)

def li(): return list(map(int, stdin.readline().split()))
def li_(): return [int(x) - 1 for x in stdin.readline().split()]
def lf(): return list(map(float, stdin.readline().split()))
def ls(): return stdin.readline().split()
def ns(): return stdin.readline().rstrip()
def lc(): return list(ns())
def ni(): return int(stdin.readline())
def nf(): return float(stdin.readline())

n = ni()
s = ns()

dic = {}
strs = set()

for st in range(n):
    stri = s[st]
    if not stri in strs:
        dic[stri] = [st, -1]
        strs.add(stri)
    else:
        dic[stri][1] = st

    for ed in range(st+1, min(st+n//2+2, n)):
        stri += s[ed]
        if not stri in strs:
            dic[stri] = [st, -1]
            strs.add(stri)
        else:
            dic[stri][1] = st

ans = 0
for key, val in list(dic.items()):
    if val[1] - val[0] >= len(key):
        ans = max(ans, len(key))

print(ans)
# Original solution
import sys
stdin = sys.stdin

sys.setrecursionlimit(10 ** 7)

def li(): return list(map(int, stdin.readline().split()))
def li_(): return [int(x) - 1 for x in stdin.readline().split()]
def lf(): return list(map(float, stdin.readline().split()))
def ls(): return stdin.readline().split()
def ns(): return stdin.readline().rstrip()
def lc(): return list(ns())
def ni(): return int(stdin.readline())
def nf(): return float(stdin.readline())

n = ni()
s = ns()

dic = {}
strs = set()

for st in range(n):
    stri = s[st]
    if not stri in strs:
        dic[stri] = [st, -1]
        strs.add(stri)
    else:
        dic[stri][1] = st

    for ed in range(st+1, min(st+n//2+2, n)):
        stri += s[ed]
        if not stri in strs:
            dic[stri] = [st, -1]
            strs.add(stri)
        else:
            dic[stri][1] = st

ans = 0
for key, val in list(dic.items()):
    if val[1] - val[0] >= len(key):
        ans = max(ans, len(key))

print(ans)

# Optimized solution
import sys
stdin = sys.stdin

sys.setrecursionlimit(10 ** 7)

def li(): return list(map(int, stdin.readline().split()))
def li_(): return [int(x) - 1 for x in stdin.readline().split()]
def lf(): return list(map(float, stdin.readline().split()))
def ls(): return stdin.readline().split()
def ns(): return stdin.readline().rstrip()
def lc(): return list(ns())
def ni(): return int(stdin.readline())
def nf(): return float(stdin.readline())

n = ni()
s = ns()

dic = {}
strs = set()

for st in range(n):
    stri = s[st]
    if not stri in strs:
        dic[stri] = [st, -1]
        strs.add(stri)
    else:
        dic[stri][1] = st

    for ed in range(st+1, min(st+n//2+2, n)):
        stri += s[ed]
        if not stri in strs:
            dic[stri] = [st, -1]
            strs.add(stri)
        else:
            dic[stri][1] = st

strlist = list(strs)
strlist.sort(key=lambda x: len(x), reverse=True)
ans = 0
for stri in strlist:
    val = dic[stri]
    leng = len(stri)
    if val[1] - val[0] >= len(stri):
        ans = len(stri)
        break

print(ans)
# Original solution
from itertools import combinations
N = int(eval(input()))
S = input().strip()
G = {chr(i):[] for i in range(97,123)}
for i in range(N):
    G[S[i]].append(i)
cmax = 0
for a in G:
    for x in combinations(G[a],2):
        i,j = x[0],x[1]
        if i>j:
            i,j = j,i
        cnt = 0
        for k in range(j-i):
            if j+k<N and S[i+k] == S[j+k]:
                cnt += 1
            else:break
        cmax = max(cmax,cnt)
        
print(cmax)

# Optimized solution
N = int(eval(input()))
S = input().strip()
cmax = 0
for k in range(1,N):
    X = S[:N-k]
    Y = S[k:]
    cur = 0
    cnt = 0
    for i in range(N-k):
        if X[i]!=Y[i]:
            cmax = max(cmax,min(cnt,k))
            cur = i
            cnt = 0
        else:
            cnt += 1
    cmax = max(cmax,min(cnt,k))
print(cmax)
# Original solution
n,s=open(0)
n=int(n)
a=0
for i in range(n):
  t=0
  for v,w in zip(s,s[i:n]):
    t=-~t*(v==w)
    if t>i:break
    if t>a:a=t
print(a)

# Optimized solution
n,s=open(0)
n=int(n)
a=0
for i in range(n):
  t=0
  for v,w in zip(s,s[i:n]):
    t=-~t*(v==w)
    a=max(a,min(t,i))
print(a)
# Original solution
import sys
input = sys.stdin.readline
sys.setrecursionlimit(10 ** 7)

N = int(eval(input()))
S = list(input().strip())

dp = [0] * N
max_leng = 0

for i in range(0, N):
    leng = 0
    idx_i = i
    sta_j = -1

    j = i - 1
    while j > -1:
        if sta_j == idx_i:
            leng = 0
            idx_i = i
            j = sta_j-1
            sta_j = -1
            continue

        if S[idx_i] == S[j]:
            if leng == 0:
                sta_j = j
            leng += 1
            idx_i -= 1
            max_leng = max(leng, max_leng)
        else:
            j += leng
            leng = 0
            idx_i = i
        j -= 1
print(max_leng)

# Optimized solution
import sys
input = sys.stdin.readline
sys.setrecursionlimit(10 ** 7)

N = int(eval(input()))
S = list(input().strip())

dp = [[0] * N for i in range(N)]
max_leng2 = 0 
for i in range(0, N):
    leng2 = 0

    j = i - 1
    while j > -1:
        if S[i] == S[j]:
            sta_j = j
            if j != 0:
                leng2 = dp[i-1][sta_j-1] + 1
            else: 
                leng2 = 1
            
            if i - sta_j < leng2:
                leng2 = i - sta_j
                
            dp[i][sta_j] = leng2
            max_leng2 = max(max_leng2, leng2)
        j -= 1
print(max_leng2)