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()

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()

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()

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()

-        dp = [[0 for i in range(N + 1)] for j in range(N + 1)] -        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]) +        for i in range(N - 1): +            ans = max(self.longest_common_subsequence(S[i:]), 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:])