{-# OPTIONS_GHC -O2 #-}
{-# LANGUAGE BangPatterns          #-}
{-# LANGUAGE CPP                   #-}
{-# LANGUAGE LambdaCase            #-}
{-# LANGUAGE MagicHash             #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE MultiWayIf            #-}
{-# LANGUAGE OverloadedStrings     #-}
{-# LANGUAGE TupleSections         #-}
{-# LANGUAGE TypeFamilies          #-}
{-# LANGUAGE ViewPatterns          #-}

import           Control.Applicative
import           Control.Exception
import           Control.Monad
import           Control.Monad.Primitive
import           Control.Monad.ST
import           Control.Monad.State.Strict
import           Data.Bool
import qualified Data.ByteString             as B
import qualified Data.ByteString.Builder     as B
import qualified Data.ByteString.Char8       as C
import qualified Data.ByteString.Internal    as B
import qualified Data.ByteString.Unsafe      as B
import           Data.Char
import qualified Data.Foldable               as F
import           Data.Function
import qualified Data.IntMap.Strict          as IM
import qualified Data.IntSet                 as IS
import qualified Data.List                   as L
import qualified Data.Map.Strict             as M
import           Data.Monoid
import           Data.Ord
import           Data.Primitive.MutVar
import qualified Data.Set                    as S
import           Data.Tuple
import qualified Data.Vector                 as V
import qualified Data.Vector.Generic         as G
import qualified Data.Vector.Generic.Mutable as GM
import qualified Data.Vector.Unboxed         as U
import qualified Data.Vector.Unboxed.Mutable as UM
import           Debug.Trace
import           Foreign                     hiding (void, newArray)
import           GHC.Exts
import qualified System.IO                   as IO
import           Unsafe.Coerce
--
import Data.Array.Base
import Data.Array.ST (runSTUArray)
import Data.Ix

main :: IO ()
main = do
    n <- readLn :: IO Int
    bs <- C.getLine
    print $ solve n bs

solve :: Int -> B.ByteString -> Int
solve n bs = maximum $ zipWith f [0..n-1] [1..n]
  where
    !sa = suffixArray bs
    !lcp = lcpArray bs sa
    f i j
        | sai + lcpi <= saj || saj + lcpi <= sai = lcpi
        | otherwise = 0
      where
        lcpi = unsafeAt lcp i
        sai = unsafeAt sa i
        saj = unsafeAt sa j


-------------------------------------------------------------------------------
rep, rev :: Monad m => Int -> (Int -> m ()) -> m ()
rep !n = U.forM_ $ U.generate n id
rev !n = U.forM_ $ U.iterateN n (subtract 1) (n - 1)
unsafeModify :: (MArray a e m, Ix i) => a i e -> Int -> (e -> e) -> m ()
unsafeModify a i f=unsafeRead a i>>=unsafeWrite a i.f
{-# INLINE unsafeModify #-}

type Index = Int
type Rank = Int
type SuffixArray = UArray Int Int
type LCPArray = UArray Int Int

-------------------------------------------------------------------------------
-- SuffixArray
suffixArray :: B.ByteString -> UArray Int Int
suffixArray bs = runSTUArray $ do
  let !n = B.length bs :: Int
      !countMax = max 0xff (n+1)

  sa    <- newArray (0,n) 0 :: ST s (STUArray s Int Int)
  rank  <- newArray (0,n) 0 :: ST s (STUArray s Int Int)
  tmp   <- newArray (0,n) 0 :: ST s (STUArray s Int Int)
  count <- newArray (0,countMax) 0 :: ST s (STUArray s Int Int)

  rep n $ \i-> do
    unsafeWrite sa i i
    unsafeWrite rank i . fromIntegral $ B.unsafeIndex bs i
  unsafeWrite sa n n
  unsafeWrite rank n 1

  forM_ (takeWhile (<=n) $ iterate (2*) 1) $ \k-> do
    -- sort sa
    rep (countMax+1) $ \i-> do
      unsafeWrite count i 0
    rep (n+1) $ \i-> do
      sai <- unsafeRead sa i
      if sai+k<=n then do
        rankik <- unsafeRead rank (sai+k)
        unsafeModify count rankik (1+)
      else unsafeModify count 0 (1+)
    rep countMax $ \i-> do
      cnti <- unsafeRead count i
      unsafeModify count (i+1) (cnti+)
    rev (n+1) $ \i-> do
      sai <- unsafeRead sa i
      rankik <- if sai + k <= n then do
                   unsafeRead rank (sai+k)
                else return 0
      j <- subtract 1 <$> unsafeRead count rankik
      unsafeWrite count rankik j
      unsafeRead sa i>>=unsafeWrite tmp j

    rep (countMax+1) $ \i-> do
      unsafeWrite count i 0
    rep (n+1) $ \i-> do
      sai <- unsafeRead tmp i
      ranki <- unsafeRead rank sai
      unsafeModify count ranki (1+)
    rep countMax $ \i-> do
      cnti <- unsafeRead count i
      unsafeModify count (i+1) (cnti+)
    rev (n+1) $ \i-> do
      sai <- unsafeRead tmp i
      ranki <- unsafeRead rank sai
      j <- subtract 1 <$> unsafeRead count ranki
      unsafeWrite count ranki j
      unsafeWrite sa j sai

    -- update rank
    sa0 <- unsafeRead sa 0
    unsafeWrite tmp sa0 1
    rep n $ \i-> do
      sai <- unsafeRead sa i
      sai1 <- unsafeRead sa (i+1)
      ranki <- unsafeRead rank sai
      ranki1 <- unsafeRead rank sai1
      if ranki == ranki1 then do
           rankik  <- if sai  + k > n then return (-1)
                      else unsafeRead rank (sai+k)
           ranki1k <- if sai1 + k > n then return (-1)
                      else unsafeRead rank (sai1+k)
           if rankik == ranki1k then do
             unsafeRead tmp sai >>= unsafeWrite tmp sai1
           else do
             unsafeRead tmp sai >>= unsafeWrite tmp sai1 . (1+)
      else do
        unsafeRead tmp sai >>= unsafeWrite tmp sai1 . (1+)

    rep (n+1) $ \i-> do
      unsafeRead tmp i >>= unsafeWrite rank i

  return sa
-------------------------------------------------------------------------------
-- Longest Common Prefix Array

lcp :: B.ByteString -> B.ByteString -> Int
lcp xs ys = go 0
   where
     !n = min (B.length xs) (B.length ys)
     go !i
       | i < n && B.unsafeIndex xs i == B.unsafeIndex ys i = go (i+1)
       | otherwise = i

calcLcp :: B.ByteString -> Int -> Int -> Int -> Int
calcLcp bs !i !j h = go $ max 0 (h-1)
   where
     !hmax = B.length bs - max i j
     go !h
       | h < hmax, B.unsafeIndex bs (i+h) == B.unsafeIndex bs (j+h) = go (h+1)
       | otherwise = h

lcpArray :: B.ByteString -> SuffixArray -> LCPArray
lcpArray bs sa = runSTUArray $ do
  let !n = B.length bs
      rank = array (0,n) . map swap $ assocs sa  :: UArray Int Int
  lcp <- newArray (0,n) 0 :: ST s (STUArray s Int Int)
  let go !i !h
        | i < n = do
            let nh = calcLcp bs i (unsafeAt sa (unsafeAt rank i - 1)) h
            unsafeWrite lcp (unsafeAt rank i - 1) nh
            go (i+1) nh
        | otherwise = return lcp
  go 0 0

type Parser a = C.ByteString -> Maybe (a, C.ByteString)

parseInt :: Parser Int
parseInt = C.readInt . C.dropWhile isSpace

parseInt2 :: Parser (Int, Int)
parseInt2 = runStateT $
    (,) <$> StateT (C.readInt . C.dropWhile isSpace)
        <*> StateT (C.readInt . B.unsafeTail)

parseInt3 :: Parser (Int, Int, Int)
parseInt3 = runStateT $
    (,,) <$> StateT (C.readInt . C.dropWhile isSpace)
        <*> StateT (C.readInt . B.unsafeTail)
        <*> StateT (C.readInt . B.unsafeTail)

parseInt4 :: Parser (Int, Int, Int, Int)
parseInt4 = runStateT $
    (,,,) <$> StateT (C.readInt . C.dropWhile isSpace)
        <*> StateT (C.readInt . B.unsafeTail)
        <*> StateT (C.readInt . B.unsafeTail)
        <*> StateT (C.readInt . B.unsafeTail)