BASE = 9973
MOD1 = 10 ** 9 + 7
MOD2 = 998244353


class RollingHash:
    # 文字列Sのローリングハッシュを構築
    # 計算量: O(N)
    def __init__(self, S, MOD):
        self.MOD = MOD
        # acc[i]はS[0:i]のハッシュ値となる
        self.acc = [0]
        a = 0
        # 累積和を計算するイメージ
        for i, c in enumerate(S):
            h = ord(c) - ord("a") + 1
            offset = pow(BASE, i, MOD)
            a = (a + (h * offset)) % MOD
            self.acc.append(a)

    # S[i:j]のハッシュ値を返す
    # 計算量: O(1)
    def hash(self, i, j):
        # 累積和を用いて部分区間の和を計算するイメージ
        # 注意: 基数によるオフセットをキャンセルする必要がある
        offset = pow(BASE, i, self.MOD)
        # 素数MODを法とする剰余類における除算は逆元を乗算することで計算できる
        # 素数MODを法とする剰余類におけるxの逆元はx^(MOD - 2)に等しい
        offset_inv = pow(offset, self.MOD - 2, self.MOD)
        return ((self.acc[j] - self.acc[i]) * offset_inv) % self.MOD


N = int(input())
S = input()

# Sのローリングハッシュを計算しておく
rolling1 = RollingHash(S, MOD1)
rolling2 = RollingHash(S, MOD2)

# 長さmのダジャレがSに含まれるならば、長さm-1のダジャレも含まれる
# つまり、長さmのダジャレがSに含まれるならば真となる述語をP(m)とすると、以下を満たす整数cが存在する:
#   - 0以上c以下の整数mについてP(m)は真
#   - cより大きい整数mについてP(m)は偽
# ※ Pは単調関数ということ
# このcを二分探索で発見すれば高速化できる！

# P(ok)は真
ok = 0
# P(ng)は偽
ng = N
while ng - ok > 1:
    # 区間[ok, ng)の中央値をmとする
    m = (ok + ng) // 2

    # 長さmのダジャレが存在するか判定する
    # 長さmのformerのハッシュ値のキャッシュ
    former_cache1 = set()
    former_cache2 = set()
    p = False
    # latterに着目してループを回す
    for i in range(m, N):
        # ダジャレ長を決め打ちしているのでjを直接計算できる
        j = i + m
        # S[i:j]がSをはみ出したらループ終了
        if j > N:
            break
        # formerのハッシュ値をキャッシュする
        former_cache1.add(rolling1.hash(i - m, i))
        former_cache2.add(rolling2.hash(i - m, i))
        # キャッシャに同一のハッシュ値が存在する場合（ハッシュ値が衝突した場合）、ダジャレになっていると判定できる
        # ハッシュ値の計算、キャッシュ存在判定は共にO(1)で処理できる！
        if (
            rolling1.hash(i, j) in former_cache1
            and rolling2.hash(i, j) in former_cache2
        ):
            p = True
    # 区間[ok, ng) を更新
    if p:
        ok = m
    else:
        ng = m
# whileループを抜けると区間[ok, ng)は[c, c + 1) になっている
ans = ok

print(ans) BASE = 9973 BASE 9973 MOD1 = 10 ** 9 + 7 MOD1 10 ** 9 + 7 10 ** 9 10 9 7 MOD2 = 998244353 MOD2 998244353 class RollingHash:
    # 文字列Sのローリングハッシュを構築
    # 計算量: O(N)
    def __init__(self, S, MOD):
        self.MOD = MOD
        # acc[i]はS[0:i]のハッシュ値となる
        self.acc = [0]
        a = 0
        # 累積和を計算するイメージ
        for i, c in enumerate(S):
            h = ord(c) - ord("a") + 1
            offset = pow(BASE, i, MOD)
            a = (a + (h * offset)) % MOD
            self.acc.append(a)

    # S[i:j]のハッシュ値を返す
    # 計算量: O(1)
    def hash(self, i, j):
        # 累積和を用いて部分区間の和を計算するイメージ
        # 注意: 基数によるオフセットをキャンセルする必要がある
        offset = pow(BASE, i, self.MOD)
        # 素数MODを法とする剰余類における除算は逆元を乗算することで計算できる
        # 素数MODを法とする剰余類におけるxの逆元はx^(MOD - 2)に等しい
        offset_inv = pow(offset, self.MOD - 2, self.MOD)
        return ((self.acc[j] - self.acc[i]) * offset_inv) % self.MOD RollingHash def __init__(self, S, MOD):
        self.MOD = MOD
        # acc[i]はS[0:i]のハッシュ値となる
        self.acc = [0]
        a = 0
        # 累積和を計算するイメージ
        for i, c in enumerate(S):
            h = ord(c) - ord("a") + 1
            offset = pow(BASE, i, MOD)
            a = (a + (h * offset)) % MOD
            self.acc.append(a)

    # S[i:j]のハッシュ値を返す
    # 計算量: O(1)
     __init__ self.MOD = MOD self.MOD self MOD MOD self.acc = [0] self.acc self acc [0] 0 a = 0 a 0 for i, c in enumerate(S):
            h = ord(c) - ord("a") + 1
            offset = pow(BASE, i, MOD)
            a = (a + (h * offset)) % MOD
            self.acc.append(a)

    # S[i:j]のハッシュ値を返す
    # 計算量: O(1)
     i c enumerate(S) enumerate S h = ord(c) - ord("a") + 1 h ord(c) - ord("a") + 1 ord(c) - ord("a") ord(c) ord c ord("a") ord "a" 1 offset = pow(BASE, i, MOD) offset pow(BASE, i, MOD) pow BASE i MOD a = (a + (h * offset)) % MOD a (a + (h * offset)) % MOD a + (h * offset) a h * offset h offset MOD self.acc.append(a) self.acc.append self.acc self acc append a self self S S MOD MOD def hash(self, i, j):
        # 累積和を用いて部分区間の和を計算するイメージ
        # 注意: 基数によるオフセットをキャンセルする必要がある
        offset = pow(BASE, i, self.MOD)
        # 素数MODを法とする剰余類における除算は逆元を乗算することで計算できる
        # 素数MODを法とする剰余類におけるxの逆元はx^(MOD - 2)に等しい
        offset_inv = pow(offset, self.MOD - 2, self.MOD)
        return ((self.acc[j] - self.acc[i]) * offset_inv) % self.MOD hash offset = pow(BASE, i, self.MOD) offset pow(BASE, i, self.MOD) pow BASE i self.MOD self MOD offset_inv = pow(offset, self.MOD - 2, self.MOD) offset_inv pow(offset, self.MOD - 2, self.MOD) pow offset self.MOD - 2 self.MOD self MOD 2 self.MOD self MOD return ((self.acc[j] - self.acc[i]) * offset_inv) % self.MOD ((self.acc[j] - self.acc[i]) * offset_inv) % self.MOD (self.acc[j] - self.acc[i]) * offset_inv self.acc[j] - self.acc[i] self.acc[j] self.acc self acc j self.acc[i] self.acc self acc i offset_inv self.MOD self MOD self self i i j j N = int(input()) N int(input()) int input() input S = input() S input() input rolling1 = RollingHash(S, MOD1) rolling1 RollingHash(S, MOD1) RollingHash S MOD1 rolling2 = RollingHash(S, MOD2) rolling2 RollingHash(S, MOD2) RollingHash S MOD2 ok = 0 ok 0 ng = N ng N while ng - ok > 1:
    # 区間[ok, ng)の中央値をmとする
    m = (ok + ng) // 2

    # 長さmのダジャレが存在するか判定する
    # 長さmのformerのハッシュ値のキャッシュ
    former_cache1 = set()
    former_cache2 = set()
    p = False
    # latterに着目してループを回す
    for i in range(m, N):
        # ダジャレ長を決め打ちしているのでjを直接計算できる
        j = i + m
        # S[i:j]がSをはみ出したらループ終了
        if j > N:
            break
        # formerのハッシュ値をキャッシュする
        former_cache1.add(rolling1.hash(i - m, i))
        former_cache2.add(rolling2.hash(i - m, i))
        # キャッシャに同一のハッシュ値が存在する場合（ハッシュ値が衝突した場合）、ダジャレになっていると判定できる
        # ハッシュ値の計算、キャッシュ存在判定は共にO(1)で処理できる！
        if (
            rolling1.hash(i, j) in former_cache1
            and rolling2.hash(i, j) in former_cache2
        ):
            p = True
    # 区間[ok, ng) を更新
    if p:
        ok = m
    else:
        ng = m
# whileループを抜けると区間[ok, ng)は[c, c + 1) になっている ng - ok > 1 ng - ok ng ok 1 m = (ok + ng) // 2 m (ok + ng) // 2 ok + ng ok ng 2 former_cache1 = set() former_cache1 set() set former_cache2 = set() former_cache2 set() set p = False p False for i in range(m, N):
        # ダジャレ長を決め打ちしているのでjを直接計算できる
        j = i + m
        # S[i:j]がSをはみ出したらループ終了
        if j > N:
            break
        # formerのハッシュ値をキャッシュする
        former_cache1.add(rolling1.hash(i - m, i))
        former_cache2.add(rolling2.hash(i - m, i))
        # キャッシャに同一のハッシュ値が存在する場合（ハッシュ値が衝突した場合）、ダジャレになっていると判定できる
        # ハッシュ値の計算、キャッシュ存在判定は共にO(1)で処理できる！
        if (
            rolling1.hash(i, j) in former_cache1
            and rolling2.hash(i, j) in former_cache2
        ):
            p = True
    # 区間[ok, ng) を更新
     i range(m, N) range m N j = i + m j i + m i m if j > N:
            break
        # formerのハッシュ値をキャッシュする
         j > N j N break former_cache1.add(rolling1.hash(i - m, i)) former_cache1.add former_cache1 add rolling1.hash(i - m, i) rolling1.hash rolling1 hash i - m i m i former_cache2.add(rolling2.hash(i - m, i)) former_cache2.add former_cache2 add rolling2.hash(i - m, i) rolling2.hash rolling2 hash i - m i m i if (
            rolling1.hash(i, j) in former_cache1
            and rolling2.hash(i, j) in former_cache2
        ):
            p = True
    # 区間[ok, ng) を更新
     rolling1.hash(i, j) in former_cache1
            and rolling2.hash(i, j) in former_cache2 rolling1.hash(i, j) in former_cache1 rolling1.hash(i, j) rolling1.hash rolling1 hash i j former_cache1 rolling2.hash(i, j) in former_cache2 rolling2.hash(i, j) rolling2.hash rolling2 hash i j former_cache2 p = True p True if p:
        ok = m
    else:
        ng = m
# whileループを抜けると区間[ok, ng)は[c, c + 1) になっている p ok = m ok m ng = m ng m ans = ok ans ok print(ans) print ans ng = m m ng ok = m m ok MOD1 = 10 ** 9 + 7 10 ** 9 + 7 MOD1 former_cache1 = set() set() former_cache1 MOD2 = 998244353 998244353 MOD2 ok = 0 0 ok rolling2 = RollingHash(S, MOD2) RollingHash(S, MOD2) rolling2 ng = N N ng rolling1 = RollingHash(S, MOD1) RollingHash(S, MOD1) rolling1 class RollingHash:
    # 文字列Sのローリングハッシュを構築
    # 計算量: O(N)
    def __init__(self, S, MOD):
        self.MOD = MOD
        # acc[i]はS[0:i]のハッシュ値となる
        self.acc = [0]
        a = 0
        # 累積和を計算するイメージ
        for i, c in enumerate(S):
            h = ord(c) - ord("a") + 1
            offset = pow(BASE, i, MOD)
            a = (a + (h * offset)) % MOD
            self.acc.append(a)

    # S[i:j]のハッシュ値を返す
    # 計算量: O(1)
    def hash(self, i, j):
        # 累積和を用いて部分区間の和を計算するイメージ
        # 注意: 基数によるオフセットをキャンセルする必要がある
        offset = pow(BASE, i, self.MOD)
        # 素数MODを法とする剰余類における除算は逆元を乗算することで計算できる
        # 素数MODを法とする剰余類におけるxの逆元はx^(MOD - 2)に等しい
        offset_inv = pow(offset, self.MOD - 2, self.MOD)
        return ((self.acc[j] - self.acc[i]) * offset_inv) % self.MOD class RollingHash:
    # 文字列Sのローリングハッシュを構築
    # 計算量: O(N)
    def __init__(self, S, MOD):
        self.MOD = MOD
        # acc[i]はS[0:i]のハッシュ値となる
        self.acc = [0]
        a = 0
        # 累積和を計算するイメージ
        for i, c in enumerate(S):
            h = ord(c) - ord("a") + 1
            offset = pow(BASE, i, MOD)
            a = (a + (h * offset)) % MOD
            self.acc.append(a)

    # S[i:j]のハッシュ値を返す
    # 計算量: O(1)
    def hash(self, i, j):
        # 累積和を用いて部分区間の和を計算するイメージ
        # 注意: 基数によるオフセットをキャンセルする必要がある
        offset = pow(BASE, i, self.MOD)
        # 素数MODを法とする剰余類における除算は逆元を乗算することで計算できる
        # 素数MODを法とする剰余類におけるxの逆元はx^(MOD - 2)に等しい
        offset_inv = pow(offset, self.MOD - 2, self.MOD)
        return ((self.acc[j] - self.acc[i]) * offset_inv) % self.MOD RollingHash N = int(input()) int(input()) N j = i + m i + m j BASE = 9973 9973 BASE S = input() input() S m = (ok + ng) // 2 (ok + ng) // 2 m p = False False p former_cache2 = set() set() former_cache2 ans = ok ok ans p = True True p
BASE = 9973
MOD1 = 10 ** 9 + 7
MOD2 = 998244353


class RollingHash:
    # 文字列Sのローリングハッシュを構築
    # 計算量: O(N)
    def __init__(self, S, MOD):
        self.MOD = MOD
        # acc[i]はS[0:i]のハッシュ値となる
        self.acc = [0]
        a = 0
        # 累積和を計算するイメージ
        for i, c in enumerate(S):
            h = ord(c) - ord("a") + 1
            offset = pow(BASE, i, MOD)
            a = (a + (h * offset)) % MOD
            self.acc.append(a)

    # S[i:j]のハッシュ値を返す
    # 計算量: O(1)
    def hash(self, i, j):
        # 累積和を用いて部分区間の和を計算するイメージ
        # 注意: 基数によるオフセットをキャンセルする必要がある
        offset = pow(BASE, i, self.MOD)
        # 素数MODを法とする剰余類における除算は逆元を乗算することで計算できる
        # 素数MODを法とする剰余類におけるxの逆元はx^(MOD - 2)に等しい
        offset_inv = pow(offset, self.MOD - 2, self.MOD)
        return ((self.acc[j] - self.acc[i]) * offset_inv) % self.MOD


N = int(input())
S = input()

# Sのローリングハッシュを計算しておく
rolling1 = RollingHash(S, MOD1)
rolling2 = RollingHash(S, MOD2)

# 長さmのダジャレがSに含まれるならば、長さm-1のダジャレも含まれる
# つまり、長さmのダジャレがSに含まれるならば真となる述語をP(m)とすると、以下を満たす整数cが存在する:
#   - 0以上c以下の整数mについてP(m)は真
#   - cより大きい整数mについてP(m)は偽
# ※ Pは単調関数ということ
# このcを二分探索で発見すれば高速化できる！

# P(ok)は真
ok = 0
# P(ng)は偽
ng = N
while ng - ok > 1:
    # 区間[ok, ng)の中央値をmとする
    m = (ok + ng) // 2

    # 長さmのダジャレが存在するか判定する
    # 長さmのformerのハッシュ値のキャッシュ
    former_cache1 = set()
    former_cache2 = set()
    p = False
    # latterに着目してループを回す
    for i in range(m, N):
        # ダジャレ長を決め打ちしているのでjを直接計算できる
        j = i + m
        # S[i:j]がSをはみ出したらループ終了
        if j > N:
            break
        # formerのハッシュ値をキャッシュする
        former_cache1.add(rolling1.hash(i - m, i))
        former_cache2.add(rolling2.hash(i - m, i))
        # キャッシャに同一のハッシュ値が存在する場合（ハッシュ値が衝突した場合）、ダジャレになっていると判定できる
        # ハッシュ値の計算、キャッシュ存在判定は共にO(1)で処理できる！
        if (
            rolling1.hash(i, j) in former_cache1
            and rolling2.hash(i, j) in former_cache2
        ):
            p = True
    # 区間[ok, ng) を更新
    if p:
        ok = m
    else:
        ng = m
# whileループを抜けると区間[ok, ng)は[c, c + 1) になっている
ans = ok

print(ans)
