CodeEff/ECCO huggingface dataset:
# Original solution
from sys import exit, setrecursionlimit, stderr
from functools import reduce
from itertools import *
from collections import defaultdict
from bisect import bisect

def read():
  return int(eval(input()))

def reads():
  return [int(x) for x in input().split()]

MOD = 10**9 + 7

N = read()

NN = N + 1
fact = [1] * NN
for i in range(1, NN):
  fact[i] = (fact[i-1] * i) % MOD

def inv(n):
  return pow(n, MOD-2, MOD)

invfact = [inv(fact[i]) for i in range(NN)]

def comb(n, k):
  return fact[n] * invfact[n-k] % MOD * invfact[k] % MOD

S = [0] * N
for k in range((N+1) // 2, N):
  S[k] = comb(k-1, N-1-k) * fact[k] % MOD * fact[N-1-k] % MOD

ans = 0
for k in range((N+1) // 2, N):
  ans = (ans + k * (S[k] - S[k-1]) % MOD) % MOD
print(ans)


# Optimized solution
from sys import exit, setrecursionlimit, stderr
from functools import reduce
from itertools import *
from collections import defaultdict
from bisect import bisect
 
def read():
  return int(eval(input()))
 
def reads():
  return [int(x) for x in input().split()]
 
MOD = 10**9 + 7
 
N = read()
 
NN = N + 1
fact = [1] * NN
for i in range(1, NN):
  fact[i] = (fact[i-1] * i) % MOD
 
def inv(n):
  return pow(n, MOD-2, MOD)
 
invfact = [0] * NN
invfact[-1] = inv(fact[-1])
for i in range(NN-2, -1, -1):
  invfact[i] = invfact[i+1] * (i+1) % MOD
 
def comb(n, k):
  return fact[n] * invfact[n-k] % MOD * invfact[k] % MOD
 
S = [0] * N
for k in range((N+1) // 2, N):
  S[k] = comb(k-1, N-1-k) * fact[k] % MOD * fact[N-1-k] % MOD
 
ans = 0
for k in range((N+1) // 2, N):
  ans = (ans + k * (S[k] - S[k-1]) % MOD) % MOD
print(ans)
# Original solution
import sys
input = lambda : sys.stdin.readline().rstrip()
sys.setrecursionlimit(max(1000, 10**9))
write = lambda x: sys.stdout.write(x+"\n")


n = int(eval(input()))

M = 10**9+7 # 出力の制限
N = n+3 # 必要なテーブルサイズ

g1 = [None] * (N+1) # 元テーブル
g2 = [None] * (N+1) #逆元テーブル
inverse = [None] * (N+1) #逆元テーブル計算用テーブル
g1[0] = g1[1] = g2[0] = g2[1] = 1
inverse[0], inverse[1] = [0, 1] 

for i in range( 2, N + 1 ):
    g1[i] = ( g1[i-1] * i ) % M 
    inverse[i] = ( -inverse[M % i] * (M//i) ) % M # ai+b==0 mod M <=> i==-b*a^(-1) <=> i^(-1)==-b^(-1)*aより
    g2[i] = (g2[i-1] * inverse[i]) % M 

def cmb(n, r, M):
    if ( r<0 or r>n ):
        return 0
    r = min(r, n-r)
    return (g1[n] * g2[r] * g2[n-r]) % M
ans = 0
prev = 0
# s = 0
for i in range((n+1)//2, n):
    tmp = (cmb(i-1, n-i-1, M) * g1[i] * g1[n-1-i])
    ans += i*(tmp-prev)
    prev = tmp
#     print(i, tmp, g1[i], g1[n-1-i])
    ans %= M
print(ans)

# Optimized solution
import sys
input = lambda : sys.stdin.readline().rstrip()
sys.setrecursionlimit(max(1000, 10**9))
write = lambda x: sys.stdout.write(x+"\n")


n = int(eval(input()))

M = 10**9+7 # 出力の制限
N = n+3 # 必要なテーブルサイズ

g1 = [None] * (N+1) # 元テーブル
g2 = [None] * (N+1) #逆元テーブル
inverse = [None] * (N+1) #逆元テーブル計算用テーブル
g1[0] = g1[1] = g2[0] = g2[1] = 1
inverse[0], inverse[1] = [0, 1] 

for i in range( 2, N + 1 ):
    g1[i] = ( g1[i-1] * i ) % M 
    inverse[i] = ( -inverse[M % i] * (M//i) ) % M # ai+b==0 mod M <=> i==-b*a^(-1) <=> i^(-1)==-b^(-1)*aより
    g2[i] = (g2[i-1] * inverse[i]) % M 

def cmb(n, r, M):
    if ( r<0 or r>n ):
        return 0
    r = min(r, n-r)
    return (g1[n] * g2[r] * g2[n-r]) % M
ans = 0
prev = 0
# s = 0
for i in range((n+1)//2, n):
    tmp = (cmb(i-1, n-i-1, M) * g1[i] * g1[n-1-i])
    ans += tmp
#     ans += i*(tmp-prev)
    prev = tmp
#     print(i, tmp, g1[i], g1[n-1-i])
    ans %= M
i = n-1
ans = (i+1) * (cmb(i-1, n-i-1, M) * g1[i] * g1[n-1-i]) - ans
ans %= M
print(ans)
# Original solution
characteristic=10**9+7
def plus(input1,input2):
    return (input1+input2)%characteristic
def minus(input1,input2):
    return (input1-input2)%characteristic
def times(input1,input2):
    return (input1*input2)%characteristic
def exponen(input1,input2):
    binaryl=list(str(bin(input2))[2:])
    binarysize=len(binaryl)
    binarylist=[int(binaryl[binarysize-1-i]) for i in range(binarysize)]
    modlist=[0 for i in range(binarysize)]
    modlist[0]=input1
    for i in range(1,binarysize):
        modlist[i]=times(modlist[i-1],modlist[i-1])
    answer=1
    for i in range(binarysize):
        if binarylist[i]==1:
            answer=times(answer,modlist[i])
    return answer
def divide(input1,input2):
    return times(input1,exponen(input2,characteristic-2))
N=int(eval(input()))
Fact=[1 for i in range(N+1)]
Finv=[1 for i in range(N+1)]
for i in range(1,N+1):
    Fact[i]=times(Fact[i-1],i)
    Finv[i]=divide(1,Fact[i])
ans=0
SGL=[0 for i in range(N)]
for K in range(N):
    if 2*K-N<0:
        continue
    SGL[K]=times(times(Fact[K],Finv[2*K-N]),Fact[K-1])
for K in range(1,N):
    ans=plus(ans,times(SGL[K]-SGL[K-1],K))
print(ans)


# Optimized solution
P=10**9+7
def egcd(a, b):
    (x, lastx) = (0, 1)
    (y, lasty) = (1, 0)
    while b != 0:
        q = a // b
        (a, b) = (b, a % b)
        (x, lastx) = (lastx - q * x, x)
        (y, lasty) = (lasty - q * y, y)
    return (lastx, lasty, a)
def inv(x):
    return egcd(x,P)[0]
N=int(eval(input()))
Fact=[0 for i in range(N+1)]
Finv=[0 for i in range(N+1)]
Fact[0]=1
Finv[0]=1
for i in range(N):
    Fact[i+1]=((i+1)*Fact[i])%P
    Finv[i+1]=(Finv[i]*inv(i+1))%P
SGN=[0 for i in range(N)]
ans=0
for k in range(N):
    if 2*k-N>=0:
        SGN[k]=(((Fact[k-1]*Fact[k])%P)*Finv[2*k-N])%P
        ans=(ans+k*(SGN[k]-SGN[k-1]))%P
print(ans)
# Original solution
#!/usr/bin/env python3
import sys

def solve(N: int, S: str):

    d = dict()

    for length in range(N//2,0,-1):
        for j in range(0,N-length+1):
            if j == 0:
                s = S[:length]
            else:
                s = s[1:]+S[j+length-1]
            if d.get(s) != None:
                if d[s]+length <= j:
                    print(length)
                    return 
            else:
                d[s] = j

    print((0))
    return


def main():
    def iterate_tokens():
        for line in sys.stdin:
            for word in line.split():
                yield word
    tokens = iterate_tokens()
    N = int(next(tokens))  # type: int
    S = next(tokens)  # type: str
    solve(N, S)

if __name__ == '__main__':
    main()


# Optimized solution
#!/usr/bin/env python3
import sys

def solve(N: int, S: str):

    a = ""
    ans = 0
    for i in range(N):
        a += S[i]
        while(len(a) > 0):
            if a in S[i+1:]:    
                ans = max(ans,len(a))
                break
            else:               
                a = a[1:]
    print(ans)
    return


def main():
    def iterate_tokens():
        for line in sys.stdin:
            for word in line.split():
                yield word
    tokens = iterate_tokens()
    N = int(next(tokens))  # type: int
    S = next(tokens)  # type: str
    solve(N, S)

if __name__ == '__main__':
    main()
# Original solution
#coding:utf-8

import sys
sys.setrecursionlimit(10**6)
write = sys.stdout.write
dbg = lambda *something : print(*something) if DEBUG else 0
DEBUG = False
def main(given = sys.stdin.readline):
    from collections import defaultdict
    input = lambda : given().rstrip()
    LMIIS = lambda : list(map(int,input().split()))
    II = lambda : int(input())
    XLMIIS = lambda x : [LMIIS() for _ in range(x)]


    P1 = 2**127-1
    P2 = 2**31-1
    base = 26
    N = II()
    S = list(map(ord,list(input())))
    pow1 = [1]*N
    pow2 = [1]*N
    for i in range(N-1):
        pow1[i+1] = pow1[i]*base%P1
        pow2[i+1] = pow2[i]*base%P2
    hash1 = [0]*(N+1)
    hash2 = [0]*(N+1)
    hash1[1] = hash2[1] = S[0]
    for i in range(2,N+1):
        hash1[i] = (hash1[i-1]*base + S[i-1]) % P1
        hash2[i] = (hash2[i-1]*base + S[i-1]) % P2
    
    def f(x):
        hashes = defaultdict(lambda : -1)
        for i in range(N-x+1):
            #S[i:i+x]
            h1 = (hash1[i+x] - hash1[i] * pow1[x]) % P1
            h2 = (hash2[i+x] - hash2[i] * pow2[x]) % P2
            if hashes[(h1,h2)] == -1:
                hashes[(h1,h2)] = i
            elif hashes[(h1,h2)] + x <= i:
                return True
        # print(hashes)
        return False
    
    ok = -1
    ng = N//2+1
    while ng-ok > 1:
        
        m = (ok+ng)//2
        # print(m)
        if f(m):
            ok = m
        else:
            ng = m
    print(ok)

if __name__ == '__main__':
    main()


# Optimized solution
#coding:utf-8

import sys
sys.setrecursionlimit(10**6)
write = sys.stdout.write
dbg = lambda *something : print(*something) if DEBUG else 0
DEBUG = False
def main(given = sys.stdin.readline):
    from collections import defaultdict
    input = lambda : given().rstrip()
    LMIIS = lambda : list(map(int,input().split()))
    II = lambda : int(input())
    XLMIIS = lambda x : [LMIIS() for _ in range(x)]


    P1 = 2**127-1
    P2 = 2**31-1
    P1 = 10**9*7
    P2 = 10**7-9
    base = 26
    N = II()
    S = list(map(ord,list(input())))
    pow1 = [1]*N
    pow2 = [1]*N
    for i in range(N-1):
        pow1[i+1] = pow1[i]*base%P1
        pow2[i+1] = pow2[i]*base%P2
    hash1 = [0]*(N+1)
    hash2 = [0]*(N+1)
    hash1[1] = hash2[1] = S[0]
    for i in range(2,N+1):
        hash1[i] = (hash1[i-1]*base + S[i-1]) % P1
        hash2[i] = (hash2[i-1]*base + S[i-1]) % P2
    
    def f(x):
        hashes = defaultdict(lambda : -1)
        for i in range(N-x+1):
            #S[i:i+x]
            h1 = (hash1[i+x] - hash1[i] * pow1[x]) % P1
            h2 = (hash2[i+x] - hash2[i] * pow2[x]) % P2
            if hashes[(h1,h2)] == -1:
                hashes[(h1,h2)] = i
            elif hashes[(h1,h2)] + x <= i:
                return True
        # print(hashes)
        return False
    
    ok = -1
    ng = N//2+1
    while ng-ok > 1:
        
        m = (ok+ng)//2
        # print(m)
        if f(m):
            ok = m
        else:
            ng = m
    print(ok)

if __name__ == '__main__':
    main()
# Original solution
import sys
input = sys.stdin.readline
from collections import *

def judge(x):
    d = defaultdict(int)
    q = deque([])
    
    for i in range(x):
        q.append(S[i])
        
    d[''.join(q)] = 0
    
    for i in range(x, N):
        q.append(S[i])
        q.popleft()
        k = ''.join(q)
        
        if k in d:
            if i-x+1-d[k]>=x:
                return True
        else:
            d[k] = i-x+1
    
    return False

def binary_search():
    l, r = 0, N
    
    while l<=r:
        m = (l+r)//2
        
        if judge(m):
            l = m+1
        else:
            r = m-1
    
    return r

N = int(eval(input()))
S = input()[:-1]
print((binary_search()))

# Optimized solution
import sys
input = sys.stdin.readline
from collections import *

def judge(x):
    d = defaultdict(int)
    
    for i in range(N-x+1):
        k = hash(S[i:i+x])
        
        if k in d:
            if i>=d[k]+x:
                return True
        else:
            d[k] = i
    
    return False

def binary_search():
    l, r = 0, N
    
    while l<=r:
        m = (l+r)//2
        
        if judge(m):
            l = m+1
        else:
            r = m-1
    
    return r

N = int(eval(input()))
S = input()[:-1]
print((binary_search()))
# Original solution
def resolve():
    import sys
    input=sys.stdin.readline
    N = int(input().rstrip())
    S = input().rstrip()

    def z_algo(s):
        """
        param s:str
        return list of int with length len(s)
        """
        # initialization
        n=len(s)
        z=[0]*n
        z[0]=n
        # construct
        L,R=0,0
        for i in range(1,n):
            if i>R: # 過去の結果が全く使えない
                L=R=i
                while(R<n and s[R-L]==s[R]): R+=1
                z[i]=R-L
                R-=1 # 0-indexedでiと比較するため
            elif z[i-L]<R-i+1:
                z[i]=z[i-L]
            else:
                L=i
                R+=1 # Rまでは確定しているので、R+1から確認
                while(R<n and s[R-L]==s[R]): R+=1
                z[i]=R-L
                R-=1
        return z

    ans = 0
    #for i in range(N-1):
    #    z = z_algo(S[i:])
    #    z = z[(len(z)+1)//2:]
    #    max_z = max(z)
    #    ans = max(ans, max_z)
    for i in range(N-1):
        z = z_algo(S[i:])
        for j in range(len(z)):
            ans=max(ans,min(j,z[j]))
    print(ans)

resolve()

# Optimized solution
def resolve():
    import sys
    input=sys.stdin.readline
    N = int(input().rstrip())
    S = input().rstrip()

    def z_algo(s):
        """
        param s:str
        return list of int with length len(s)
        """
        # initialization
        n=len(s)
        z=[0]*n
        z[0]=n
        # construct
        L,R=0,0
        for i in range(1,n):
            if i>R: # 過去の結果が全く使えない
                L=R=i
                while(R<n and s[R-L]==s[R]): R+=1
                z[i]=R-L
                R-=1 # 0-indexedでiと比較するため
            elif z[i-L]<R-i+1:
                z[i]=z[i-L]
            else:
                L=i
                R+=1 # Rまでは確定しているので、R+1から確認
                while(R<n and s[R-L]==s[R]): R+=1
                z[i]=R-L
                R-=1
        return z

    ans = 0
    for i in range(N-1):
        z = z_algo(S[i:])
        for j, _z in enumerate(z):
            ans=max(ans,min(j,z[j]))
    print(ans)

resolve()
# Original solution
def Z_algo(S):
    n = len(S)
    LCP = [0]*n
    i = 1
    j = 0
    c = 0
    for i in range(1, n):
        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

def main():
    n = int(eval(input()))
    s = input().rstrip()
    ans = 0
    for i in range(n):
        lcp = Z_algo(s[i:])
        for j, l in enumerate(lcp):
            x = min(l, j)
            ans = max(ans, x)
    print(ans)

if __name__ == "__main__":
    main()

# Optimized solution
def check(x, N, S):
    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

def main():
    n = int(eval(input()))
    s = input().rstrip()
    l = 0
    r = n
    while l < r:
        mid = (l+r+1) // 2
        if check(mid, n, s):
            l = mid
        else:
            r = mid-1
    print(((l+r+1)//2))

if __name__ == "__main__":
    main()
# Original solution
from typing import List, Tuple


class RollingHash:
    """Rolling Hash"""

    __slots__ = ["_base", "_mod", "_hash", "_power"]

    def __init__(self, source: str, base: int = 1007, mod: int = 10 ** 9 + 7):
        self._base = base
        self._mod = mod
        self._hash, self._power = self._build(source)

    def _build(self, source: str) -> Tuple[List[int], List[int]]:
        """Compute "hash" and "mod of power of base" of interval [0, right)."""
        length = len(source)
        hash_, power = [0] * (length + 1), [0] * (length + 1)
        power[0] = 1
        for i, c in enumerate(source, 1):
            hash_[i] = (hash_[i - 1] * self._base + ord(c)) % self._mod
            power[i] = power[i - 1] * self._base % self._mod
        return hash_, power

    def get_hash(self, left: int, right: int):
        """Return hash of interval [left, right)."""
        return (
            self._hash[right] - self._hash[left] * self._power[right - left]
        ) % self._mod


def abc141_e():
    # https://atcoder.jp/contests/abc141/tasks/abc141_e
    N = int(eval(input()))
    S = input().rstrip()

    rh = RollingHash(S)
    ok, ng = 0, N // 2 + 1
    while ng - ok > 1:
        mid = (ok + ng) // 2
        flg = False
        memo = set()
        for i in range(N - 2 * mid + 1):
            memo.add(rh.get_hash(i, i + mid))
            if rh.get_hash(i + mid, i + 2 * mid) in memo:
                flg = True
                break
        if flg:
            ok = mid  # next mid will be longer
        else:
            ng = mid  # next mid will be shorter

    print(ok)  # max length of substrings appeared twice or more


if __name__ == "__main__":
    abc141_e()


# Optimized solution
# ABC141E - Who Says a Pun?
def resolve():
    N = int(eval(input()))
    S = input().rstrip()
    ok, ng = 0, N // 2 + 1
    while ng - ok > 1:
        mid = (ok + ng) // 2
        flg = False
        memo = set()
        for i in range(N - 2 * mid + 1):
            memo.add(hash(S[i : i + mid]))
            if hash(S[i + mid : i + 2 * mid]) in memo:
                flg = True
                break
        if flg:
            ok = mid  # next mid will be longer
        else:
            ng = mid  # next mid will be shorter
    print(ok)  # max length of substrings appeared twice or more


resolve()
# Original solution
from typing import List, Tuple


class RollingHash:
    """Rolling Hash"""

    __slots__ = ["_base", "_mod", "_hash", "_power"]

    def __init__(self, source: str, base: int = 1007, mod: int = 10 ** 9 + 7):
        self._base = base
        self._mod = mod
        self._hash, self._power = self._build(source)

    def _build(self, source: str) -> Tuple[List[int], List[int]]:
        """Compute "hash" and "mod of power of base" of interval [0, right)."""
        hash_, power = [0] * (len(source) + 1), [0] * (len(source) + 1)
        power[0] = 1
        for i, c in enumerate(source, 1):
            hash_[i] = (hash_[i - 1] * self._base + ord(c)) % self._mod
            power[i] = power[i - 1] * self._base % self._mod
        return hash_, power

    def get_hash(self, left: int, right: int):
        """Return hash of interval [left, right)."""
        return (
            self._hash[right] - self._hash[left] * self._power[right - left]
        ) % self._mod


def abc141_e():
    # https://atcoder.jp/contests/abc141/tasks/abc141_e
    N = int(eval(input()))
    S = input().rstrip()

    rh = RollingHash(S)
    ok, ng = 0, N // 2 + 1
    while ng - ok > 1:
        mid = (ok + ng) // 2
        flg = False
        memo = set()
        for i in range(N - 2 * mid + 1):
            memo.add(rh.get_hash(i, i + mid))
            if rh.get_hash(i + mid, i + 2 * mid) in memo:
                flg = True
                break
        if flg:
            ok = mid  # next mid will be longer
        else:
            ng = mid  # next mid will be shorter

    print(ok)  # max length of substrings appeared twice or more


if __name__ == "__main__":
    abc141_e()


# Optimized solution
class RollingHash:
    """Rolling Hash"""

    __slots__ = ["_base", "_mod", "_hash", "_power"]

    def __init__(self, source: str, base: int = 1007, mod: int = 10 ** 9 + 7) -> None:
        self._base = base
        self._mod = mod
        self._hash = [0] * (len(source) + 1)
        self._power = [0] * (len(source) + 1)

        self._power[0] = 1
        for i, c in enumerate(source, 1):
            self._hash[i] = (self._hash[i - 1] * self._base + ord(c)) % self._mod
            self._power[i] = self._power[i - 1] * self._base % self._mod

    def get_hash(self, left: int, right: int) -> int:
        """Return hash of interval [left, right)."""
        return (
            self._hash[right] - self._hash[left] * self._power[right - left]
        ) % self._mod


def abc141_e():
    # https://atcoder.jp/contests/abc141/tasks/abc141_e
    N = int(eval(input()))
    S = input().rstrip()

    rh = RollingHash(S)
    ok, ng = 0, N // 2 + 1
    while ng - ok > 1:
        mid = (ok + ng) // 2
        flg = False
        memo = set()
        for i in range(N - 2 * mid + 1):
            memo.add(rh.get_hash(i, i + mid))
            if rh.get_hash(i + mid, i + 2 * mid) in memo:
                flg = True
                break
        if flg:
            ok = mid  # next mid will be longer
        else:
            ng = mid  # next mid will be shorter

    print(ok)  # max length of substrings appeared twice or more


if __name__ == "__main__":
    abc141_e()
# Original solution
import sys
input = sys.stdin.readline
N=int(eval(input()))
S=input().strip()
dp=[[0]*(N+1) for _ in range(N+1)]
for i in range(N-1,-1,-1):
    for j in range(N-1,i,-1):
        if S[i]==S[j]:
            dp[i][j]=max(dp[i][j],dp[i+1][j+1]+1)
#print(dp)
ans=0
for i in range(N):
    for j in range(N):
        ans=max(ans,min(dp[i][j],j-i))
print(ans)


# Optimized solution
import sys
input = sys.stdin.readline
N=int(eval(input()))
S=input().strip()
"""dp=[[0]*(N+1) for _ in range(N+1)]
for i in range(N-1,-1,-1):
    for j in range(N-1,i,-1):
        if S[i]==S[j]:
            dp[i][j]=max(dp[i][j],dp[i+1][j+1]+1)
#print(dp)
ans=0
for i in range(N):
    for j in range(N):
        ans=max(ans,min(dp[i][j],j-i))
print(ans)
"""

def Z_algo(S):
    n = len(S)
    LCP = [0]*n
    i = 1
    j = 0
    c = 0#最も末尾側まで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

ans=0
for i in range(N):
    LCP=Z_algo(S[i:])
    #print(LCP)
    for j , l in enumerate(LCP):
        tmp=min(l,j)
        ans=max(ans,tmp)
print(ans)
# Original solution
import sys

input = sys.stdin.readline


class AtCoder:
    def main(self):
        N = int(eval(input()))
        S = input().rstrip()

        dp = [[0 for i in range(N + 1)] for j in range(N + 1)]
        ans = 0
        for i in range(N)[::-1]:
            for j in range(N)[::-1]:
                if S[i] == S[j]:
                    dp[i][j] = min(dp[i + 1][j + 1] + 1, abs(i - j))
                    ans = max(ans, dp[i][j])
        print(ans)


# Run main
if __name__ == '__main__':
    AtCoder().main()


# Optimized solution
import sys

input = sys.stdin.readline


class AtCoder:
    def main(self):
        N = int(eval(input()))
        S = input().rstrip()

        ans = 0
        for i in range(N - 1):
            ans = max(self.longest_common_subsequence(S[i:]), ans)

        print(ans)

    # Z-Algorithm を使った連続する部分文字列長の計算
    def longest_common_subsequence(self, s: str) -> int:
        z = [0] * len(s)
        z[0] = len(s)

        # Z-box の初期値
        z_box_start, z_box_end, = 0, 0

        # １文字目から一文字ずつチェック
        for i in range(1, len(s)):
            # Z-box の範囲外
            if i > z_box_end:
                # 一文字ずつチェックする
                while i + z[i] < len(s) and s[z[i]] == s[z[i] + i]:
                    z[i] += 1
                # 新しい Z-box が見つかった場合
                if z[i] > 0:
                    z_box_start, z_box_end = i, i + z[i] - 1

            # Z-box の範囲内
            else:
                # 計算済みの Z-Value(一致する文字列長)を利用
                # 一致範囲が Z-Box の範囲内
                if z[i - z_box_start] <= z_box_end - i:
                    # 計算済みの Z-Value を利用
                    z[i] = z[i - z_box_start]

                # 一致範囲が Z-Box の範囲外
                else:
                    # Z-Box の範囲外 の部分を, 一文字ずつチェックする
                    check_target = z_box_end + 1
                    while check_target < len(s) and s[check_target] == s[check_target - i]:
                        check_target += 1
                    z[i] = check_target - i
                    # Z-Box の更新
                    z_box_start, z_box_end = i, check_target - 1

        # "重ならず" の制約を値に反映          
        for i, v in enumerate(z):
            z[i] = min(v, i)

        # 最大長を返却
        return max(z[1:])


# Run main
if __name__ == '__main__':
    AtCoder().main()