import random

class RollingHash(object):
    def __init__(self, S: str, MOD: int = 998244353, BASE: int = 37):
        self.S = S
        self.N = N = len(S)
        self.MOD = MOD
        self.BASE = BASE
        self.S_arr = [ord(x) for x in S]

        self.POWER = [1] * (N + 1)
        self.HASH  = [0] * (N + 1)
        p, h = 1, 0
        for i in range(N):
            self.POWER[i + 1] = p = (p * BASE) % MOD
            self.HASH[i + 1] = h = (h * BASE + self.S_arr[i]) % MOD

    def hash(self, l: int, r: int):
        # get hash for S[l:r]
        _hash = (self.HASH[r] - self.HASH[l] * self.POWER[r - l]) % self.MOD
        return _hash

# 汎用的な二分探索のテンプレ
# check_func(x) = true となるxの最小値を探索する
# left : check_func(x) = falseとなるx
# right: check_func(x) = true となるx
# search_max: check_func(x) = true となるxの"最大値"を探索する場合はTrue
# check_func: search_max = False の場合、answer <= x であればcheck_func(x) = true となるような関数
#             search_max = True  の場合、x <= answer であればcheck_func(x) = true となるような関数
def binary_search(left: int, right: int, check_func, search_max:bool=False):
    # ok と ng のどちらが大きいかわからないことを考慮
    while abs(right - left) > 1:
        mid = (left + right) // 2
    
        if check_func(mid) ^ search_max:
            right = mid
        else:
            left = mid

    return left if search_max else right

def main():
    N = int(input())
    S = input()
    MOD = 10 ** 18 + 3
    # BASEはMOD未満で1でない奇数とする
    BASE = random.randint(2, MOD // 2 - 1) * 2 + 1
    rs = RollingHash(S, MOD=MOD, BASE=BASE)

    D = [0] * N
    # 長さLの部分文字列同士が、範囲を重ねず同じ文字列となる場合はTrue,
    # そうでない場合はFalseを返す関数
    def isOK(L):
        if L == 0:
            return True
        
        for i in range(min(L, N - 2 * L + 1)):
            D[i] = rs.hash(i, i + L)
        hashes = set()
        for i in range(L, N - L + 1):
            # 自分より開始点がL以上前の文字列のハッシュを検索対象に追加
            hashes.add(D[i - L])
            D[i] = v = rs.hash(i, i + L)
            # ハッシュが衝突したら、範囲を重ねず同じ文字列となる文字列が存在することになる
            if v in hashes:
                return True
        return False

    # 範囲が重ならないことが条件にあるので、答えは最大でもN // 2（文字列の長さの半分）となる。
    # ただし、二分探索する上で絶対に答えとならない範囲を含めたいので、
    # 探索範囲としてはN // 2 + 1 と、+1しておく。
    ans = binary_search(0, N // 2 + 1, isOK, search_max=True)
    print(ans)

if __name__ == '__main__':
    main() import random random class RollingHash(object):
    def __init__(self, S: str, MOD: int = 998244353, BASE: int = 37):
        self.S = S
        self.N = N = len(S)
        self.MOD = MOD
        self.BASE = BASE
        self.S_arr = [ord(x) for x in S]

        self.POWER = [1] * (N + 1)
        self.HASH  = [0] * (N + 1)
        p, h = 1, 0
        for i in range(N):
            self.POWER[i + 1] = p = (p * BASE) % MOD
            self.HASH[i + 1] = h = (h * BASE + self.S_arr[i]) % MOD

    def hash(self, l: int, r: int):
        # get hash for S[l:r]
        _hash = (self.HASH[r] - self.HASH[l] * self.POWER[r - l]) % self.MOD
        return _hash

# 汎用的な二分探索のテンプレ
# check_func(x) = true となるxの最小値を探索する
# left : check_func(x) = falseとなるx
# right: check_func(x) = true となるx
# search_max: check_func(x) = true となるxの"最大値"を探索する場合はTrue
# check_func: search_max = False の場合、answer <= x であればcheck_func(x) = true となるような関数
#             search_max = True  の場合、x <= answer であればcheck_func(x) = true となるような関数 RollingHash def __init__(self, S: str, MOD: int = 998244353, BASE: int = 37):
        self.S = S
        self.N = N = len(S)
        self.MOD = MOD
        self.BASE = BASE
        self.S_arr = [ord(x) for x in S]

        self.POWER = [1] * (N + 1)
        self.HASH  = [0] * (N + 1)
        p, h = 1, 0
        for i in range(N):
            self.POWER[i + 1] = p = (p * BASE) % MOD
            self.HASH[i + 1] = h = (h * BASE + self.S_arr[i]) % MOD

     __init__ self.S = S self.S self S S self.N = N = len(S) self.N self N len(S) len S = N = len(S) N len(S) self.MOD = MOD self.MOD self MOD MOD self.BASE = BASE self.BASE self BASE BASE ord(x) for x in S for x in S x S for x in S ord(x) ord x self.S_arr = [ord(x) for x in S] self.S_arr self S_arr [ord(x) for x in S] self.POWER = [1] * (N + 1) self.POWER self POWER [1] * (N + 1) [1] 1 N + 1 N 1 self.HASH  = [0] * (N + 1) self.HASH self HASH [0] * (N + 1) [0] 0 N + 1 N 1 p, h = 1, 0 p 1 h 0 for i in range(N):
            self.POWER[i + 1] = p = (p * BASE) % MOD
            self.HASH[i + 1] = h = (h * BASE + self.S_arr[i]) % MOD

     i range(N) range N self.POWER[i + 1] = p = (p * BASE) % MOD self.POWER[i + 1] self.POWER self POWER i + 1 i 1 (p * BASE) % MOD p * BASE p BASE MOD = p = (p * BASE) % MOD p (p * BASE) % MOD self.HASH[i + 1] = h = (h * BASE + self.S_arr[i]) % MOD self.HASH[i + 1] self.HASH self HASH i + 1 i 1 (h * BASE + self.S_arr[i]) % MOD h * BASE + self.S_arr[i] h * BASE h BASE self.S_arr[i] self.S_arr self S_arr i MOD = h = (h * BASE + self.S_arr[i]) % MOD h (h * BASE + self.S_arr[i]) % MOD self self S: str S MOD: int = 998244353 MOD 998244353 BASE: int = 37 BASE 37 def hash(self, l: int, r: int):
        # get hash for S[l:r]
        _hash = (self.HASH[r] - self.HASH[l] * self.POWER[r - l]) % self.MOD
        return _hash

# 汎用的な二分探索のテンプレ
# check_func(x) = true となるxの最小値を探索する
# left : check_func(x) = falseとなるx
# right: check_func(x) = true となるx
# search_max: check_func(x) = true となるxの"最大値"を探索する場合はTrue
# check_func: search_max = False の場合、answer <= x であればcheck_func(x) = true となるような関数
#             search_max = True  の場合、x <= answer であればcheck_func(x) = true となるような関数 hash _hash = (self.HASH[r] - self.HASH[l] * self.POWER[r - l]) % self.MOD _hash (self.HASH[r] - self.HASH[l] * self.POWER[r - l]) % self.MOD self.HASH[r] - self.HASH[l] * self.POWER[r - l] self.HASH[r] self.HASH self HASH r self.HASH[l] * self.POWER[r - l] self.HASH[l] self.HASH self HASH l self.POWER[r - l] self.POWER self POWER r - l r l self.MOD self MOD return _hash _hash self self l: int l r: int r object def binary_search(left: int, right: int, check_func, search_max:bool=False):
    # ok と ng のどちらが大きいかわからないことを考慮
    while abs(right - left) > 1:
        mid = (left + right) // 2
    
        if check_func(mid) ^ search_max:
            right = mid
        else:
            left = mid

    return left if search_max else right binary_search while abs(right - left) > 1:
        mid = (left + right) // 2
    
        if check_func(mid) ^ search_max:
            right = mid
        else:
            left = mid

     abs(right - left) > 1 abs(right - left) abs right - left right left 1 mid = (left + right) // 2 mid (left + right) // 2 left + right left right 2 if check_func(mid) ^ search_max:
            right = mid
        else:
            left = mid

     check_func(mid) ^ search_max check_func(mid) check_func mid search_max right = mid right mid left = mid left mid return left if search_max else right left if search_max else right search_max left right left: int left right: int right check_func check_func search_max:bool=False search_max False def main():
    N = int(input())
    S = input()
    MOD = 10 ** 18 + 3
    # BASEはMOD未満で1でない奇数とする
    BASE = random.randint(2, MOD // 2 - 1) * 2 + 1
    rs = RollingHash(S, MOD=MOD, BASE=BASE)

    D = [0] * N
    # 長さLの部分文字列同士が、範囲を重ねず同じ文字列となる場合はTrue,
    # そうでない場合はFalseを返す関数
    def isOK(L):
        if L == 0:
            return True
        
        for i in range(min(L, N - 2 * L + 1)):
            D[i] = rs.hash(i, i + L)
        hashes = set()
        for i in range(L, N - L + 1):
            # 自分より開始点がL以上前の文字列のハッシュを検索対象に追加
            hashes.add(D[i - L])
            D[i] = v = rs.hash(i, i + L)
            # ハッシュが衝突したら、範囲を重ねず同じ文字列となる文字列が存在することになる
            if v in hashes:
                return True
        return False

    # 範囲が重ならないことが条件にあるので、答えは最大でもN // 2（文字列の長さの半分）となる。
    # ただし、二分探索する上で絶対に答えとならない範囲を含めたいので、
    # 探索範囲としてはN // 2 + 1 と、+1しておく。
    ans = binary_search(0, N // 2 + 1, isOK, search_max=True)
    print(ans) main N = int(input()) N int(input()) int input() input S = input() S input() input MOD = 10 ** 18 + 3 MOD 10 ** 18 + 3 10 ** 18 10 18 3 BASE = random.randint(2, MOD // 2 - 1) * 2 + 1 BASE random.randint(2, MOD // 2 - 1) * 2 + 1 random.randint(2, MOD // 2 - 1) * 2 random.randint(2, MOD // 2 - 1) random.randint random randint 2 MOD // 2 - 1 MOD // 2 MOD 2 1 2 1 rs = RollingHash(S, MOD=MOD, BASE=BASE) rs RollingHash(S, MOD=MOD, BASE=BASE) RollingHash S MOD=MOD MOD MOD BASE=BASE BASE BASE D = [0] * N D [0] * N [0] 0 N def isOK(L):
        if L == 0:
            return True
        
        for i in range(min(L, N - 2 * L + 1)):
            D[i] = rs.hash(i, i + L)
        hashes = set()
        for i in range(L, N - L + 1):
            # 自分より開始点がL以上前の文字列のハッシュを検索対象に追加
            hashes.add(D[i - L])
            D[i] = v = rs.hash(i, i + L)
            # ハッシュが衝突したら、範囲を重ねず同じ文字列となる文字列が存在することになる
            if v in hashes:
                return True
        return False

    # 範囲が重ならないことが条件にあるので、答えは最大でもN // 2（文字列の長さの半分）となる。
    # ただし、二分探索する上で絶対に答えとならない範囲を含めたいので、
    # 探索範囲としてはN // 2 + 1 と、+1しておく。
     isOK if L == 0:
            return True
        
         L == 0 L 0 return True True for i in range(min(L, N - 2 * L + 1)):
            D[i] = rs.hash(i, i + L)
         i range(min(L, N - 2 * L + 1)) range min(L, N - 2 * L + 1) min L N - 2 * L + 1 N - 2 * L N 2 * L 2 L 1 D[i] = rs.hash(i, i + L) D[i] D i rs.hash(i, i + L) rs.hash rs hash i i + L i L hashes = set() hashes set() set for i in range(L, N - L + 1):
            # 自分より開始点がL以上前の文字列のハッシュを検索対象に追加
            hashes.add(D[i - L])
            D[i] = v = rs.hash(i, i + L)
            # ハッシュが衝突したら、範囲を重ねず同じ文字列となる文字列が存在することになる
            if v in hashes:
                return True
         i range(L, N - L + 1) range L N - L + 1 N - L N L 1 hashes.add(D[i - L]) hashes.add hashes add D[i - L] D i - L i L D[i] = v = rs.hash(i, i + L) D[i] D i rs.hash(i, i + L) rs.hash rs hash i i + L i L = v = rs.hash(i, i + L) v rs.hash(i, i + L) if v in hashes:
                return True
         v in hashes v hashes return True True return False False L L ans = binary_search(0, N // 2 + 1, isOK, search_max=True) ans binary_search(0, N // 2 + 1, isOK, search_max=True) binary_search 0 N // 2 + 1 N // 2 N 2 1 isOK search_max=True search_max True print(ans) print ans if __name__ == '__main__':
    main() __name__ == '__main__' __name__ '__main__' main() main class RollingHash(object):
    def __init__(self, S: str, MOD: int = 998244353, BASE: int = 37):
        self.S = S
        self.N = N = len(S)
        self.MOD = MOD
        self.BASE = BASE
        self.S_arr = [ord(x) for x in S]

        self.POWER = [1] * (N + 1)
        self.HASH  = [0] * (N + 1)
        p, h = 1, 0
        for i in range(N):
            self.POWER[i + 1] = p = (p * BASE) % MOD
            self.HASH[i + 1] = h = (h * BASE + self.S_arr[i]) % MOD

    def hash(self, l: int, r: int):
        # get hash for S[l:r]
        _hash = (self.HASH[r] - self.HASH[l] * self.POWER[r - l]) % self.MOD
        return _hash

# 汎用的な二分探索のテンプレ
# check_func(x) = true となるxの最小値を探索する
# left : check_func(x) = falseとなるx
# right: check_func(x) = true となるx
# search_max: check_func(x) = true となるxの"最大値"を探索する場合はTrue
# check_func: search_max = False の場合、answer <= x であればcheck_func(x) = true となるような関数
#             search_max = True  の場合、x <= answer であればcheck_func(x) = true となるような関数 class RollingHash(object):
    def __init__(self, S: str, MOD: int = 998244353, BASE: int = 37):
        self.S = S
        self.N = N = len(S)
        self.MOD = MOD
        self.BASE = BASE
        self.S_arr = [ord(x) for x in S]

        self.POWER = [1] * (N + 1)
        self.HASH  = [0] * (N + 1)
        p, h = 1, 0
        for i in range(N):
            self.POWER[i + 1] = p = (p * BASE) % MOD
            self.HASH[i + 1] = h = (h * BASE + self.S_arr[i]) % MOD

    def hash(self, l: int, r: int):
        # get hash for S[l:r]
        _hash = (self.HASH[r] - self.HASH[l] * self.POWER[r - l]) % self.MOD
        return _hash

# 汎用的な二分探索のテンプレ
# check_func(x) = true となるxの最小値を探索する
# left : check_func(x) = falseとなるx
# right: check_func(x) = true となるx
# search_max: check_func(x) = true となるxの"最大値"を探索する場合はTrue
# check_func: search_max = False の場合、answer <= x であればcheck_func(x) = true となるような関数
#             search_max = True  の場合、x <= answer であればcheck_func(x) = true となるような関数 RollingHash def main():
    N = int(input())
    S = input()
    MOD = 10 ** 18 + 3
    # BASEはMOD未満で1でない奇数とする
    BASE = random.randint(2, MOD // 2 - 1) * 2 + 1
    rs = RollingHash(S, MOD=MOD, BASE=BASE)

    D = [0] * N
    # 長さLの部分文字列同士が、範囲を重ねず同じ文字列となる場合はTrue,
    # そうでない場合はFalseを返す関数
    def isOK(L):
        if L == 0:
            return True
        
        for i in range(min(L, N - 2 * L + 1)):
            D[i] = rs.hash(i, i + L)
        hashes = set()
        for i in range(L, N - L + 1):
            # 自分より開始点がL以上前の文字列のハッシュを検索対象に追加
            hashes.add(D[i - L])
            D[i] = v = rs.hash(i, i + L)
            # ハッシュが衝突したら、範囲を重ねず同じ文字列となる文字列が存在することになる
            if v in hashes:
                return True
        return False

    # 範囲が重ならないことが条件にあるので、答えは最大でもN // 2（文字列の長さの半分）となる。
    # ただし、二分探索する上で絶対に答えとならない範囲を含めたいので、
    # 探索範囲としてはN // 2 + 1 と、+1しておく。
    ans = binary_search(0, N // 2 + 1, isOK, search_max=True)
    print(ans) def main():
    N = int(input())
    S = input()
    MOD = 10 ** 18 + 3
    # BASEはMOD未満で1でない奇数とする
    BASE = random.randint(2, MOD // 2 - 1) * 2 + 1
    rs = RollingHash(S, MOD=MOD, BASE=BASE)

    D = [0] * N
    # 長さLの部分文字列同士が、範囲を重ねず同じ文字列となる場合はTrue,
    # そうでない場合はFalseを返す関数
    def isOK(L):
        if L == 0:
            return True
        
        for i in range(min(L, N - 2 * L + 1)):
            D[i] = rs.hash(i, i + L)
        hashes = set()
        for i in range(L, N - L + 1):
            # 自分より開始点がL以上前の文字列のハッシュを検索対象に追加
            hashes.add(D[i - L])
            D[i] = v = rs.hash(i, i + L)
            # ハッシュが衝突したら、範囲を重ねず同じ文字列となる文字列が存在することになる
            if v in hashes:
                return True
        return False

    # 範囲が重ならないことが条件にあるので、答えは最大でもN // 2（文字列の長さの半分）となる。
    # ただし、二分探索する上で絶対に答えとならない範囲を含めたいので、
    # 探索範囲としてはN // 2 + 1 と、+1しておく。
    ans = binary_search(0, N // 2 + 1, isOK, search_max=True)
    print(ans) main def binary_search(left: int, right: int, check_func, search_max:bool=False):
    # ok と ng のどちらが大きいかわからないことを考慮
    while abs(right - left) > 1:
        mid = (left + right) // 2
    
        if check_func(mid) ^ search_max:
            right = mid
        else:
            left = mid

    return left if search_max else right def binary_search(left: int, right: int, check_func, search_max:bool=False):
    # ok と ng のどちらが大きいかわからないことを考慮
    while abs(right - left) > 1:
        mid = (left + right) // 2
    
        if check_func(mid) ^ search_max:
            right = mid
        else:
            left = mid

    return left if search_max else right binary_search
import random

class RollingHash(object):
    def __init__(self, S: str, MOD: int = 998244353, BASE: int = 37):
        self.S = S
        self.N = N = len(S)
        self.MOD = MOD
        self.BASE = BASE
        self.S_arr = [ord(x) for x in S]

        self.POWER = [1] * (N + 1)
        self.HASH  = [0] * (N + 1)
        p, h = 1, 0
        for i in range(N):
            self.POWER[i + 1] = p = (p * BASE) % MOD
            self.HASH[i + 1] = h = (h * BASE + self.S_arr[i]) % MOD

    def hash(self, l: int, r: int):
        # get hash for S[l:r]
        _hash = (self.HASH[r] - self.HASH[l] * self.POWER[r - l]) % self.MOD
        return _hash

# 汎用的な二分探索のテンプレ
# check_func(x) = true となるxの最小値を探索する
# left : check_func(x) = falseとなるx
# right: check_func(x) = true となるx
# search_max: check_func(x) = true となるxの"最大値"を探索する場合はTrue
# check_func: search_max = False の場合、answer <= x であればcheck_func(x) = true となるような関数
#             search_max = True  の場合、x <= answer であればcheck_func(x) = true となるような関数
def binary_search(left: int, right: int, check_func, search_max:bool=False):
    # ok と ng のどちらが大きいかわからないことを考慮
    while abs(right - left) > 1:
        mid = (left + right) // 2
    
        if check_func(mid) ^ search_max:
            right = mid
        else:
            left = mid

    return left if search_max else right

def main():
    N = int(input())
    S = input()
    MOD = 10 ** 18 + 3
    # BASEはMOD未満で1でない奇数とする
    BASE = random.randint(2, MOD // 2 - 1) * 2 + 1
    rs = RollingHash(S, MOD=MOD, BASE=BASE)

    D = [0] * N
    # 長さLの部分文字列同士が、範囲を重ねず同じ文字列となる場合はTrue,
    # そうでない場合はFalseを返す関数
    def isOK(L):
        if L == 0:
            return True
        
        for i in range(min(L, N - 2 * L + 1)):
            D[i] = rs.hash(i, i + L)
        hashes = set()
        for i in range(L, N - L + 1):
            # 自分より開始点がL以上前の文字列のハッシュを検索対象に追加
            hashes.add(D[i - L])
            D[i] = v = rs.hash(i, i + L)
            # ハッシュが衝突したら、範囲を重ねず同じ文字列となる文字列が存在することになる
            if v in hashes:
                return True
        return False

    # 範囲が重ならないことが条件にあるので、答えは最大でもN // 2（文字列の長さの半分）となる。
    # ただし、二分探索する上で絶対に答えとならない範囲を含めたいので、
    # 探索範囲としてはN // 2 + 1 と、+1しておく。
    ans = binary_search(0, N // 2 + 1, isOK, search_max=True)
    print(ans)

if __name__ == '__main__':
    main()