#include <bits/stdc++.h>
using namespace std;
using LL = long long;
namespace _buff {
const size_t BUFF = 1 << 19;
char buff[BUFF], *begin = buff, *end = buff;
char getc() {
  if (begin == end) {
    begin = buff;
    end = buff + fread(buff, 1, BUFF, stdin);
  }
  return begin == end ? -1 : *begin++;
}
}  // namespace _buff
LL read() {
  using namespace _buff;
  LL ret = 0;
  bool pos = true;
  char c = getc();
  for (; (c < '0' || c > '9') && c != '-'; c = getc()) {
    assert(~c);
  }
  if (c == '-') {
    pos = false;
    c = getc();
  }
  for (; c >= '0' && c <= '9'; c = getc()) {
    ret = (ret << 3) + (ret << 1) + (c ^ 48);
  }
  return pos ? ret : -ret;
}
size_t reads(int *s) {
  using namespace _buff;
  size_t ret = 0;
  char c = getc();
  for (; c < 'a' || c > 'z'; c = getc()) {
    assert(~c);
  }
  for (; c >= 'a' && c <= 'z'; c = getc()) {
    s[ret++] = c - 'a';
  }
  return ret;
}
const size_t N = 4e5 + 5;
int n, s[N];
namespace SegTree {
const size_t NN = N * 20;
int rt[N], lc[NN], rc[NN], sum[NN], tot;
void add(int x, int &y, int tl, int tr, int p) {
  y = ++tot;
  sum[y] = sum[x] + 1;
  if (tl + 1 == tr) return;
  int mid = (tl + tr) >> 1;
  if (p < mid) {
    add(lc[x], lc[y], tl, mid, p);
    rc[y] = rc[x];
  } else {
    add(rc[x], rc[y], mid, tr, p);
    lc[y] = lc[x];
  }
}
void add(int id1, int id2, int p) { add(rt[id1], rt[id2], 0, n, p); }
bool query(int x, int y, int tl, int tr, int l, int r) {
  if (tl >= r || tr <= l || !y) return false;
  if (l <= tl && tr <= r) {
    return sum[y] - sum[x];
  }
  int mid = (tl + tr) >> 1;
  return query(lc[x], lc[y], tl, mid, l, r) |
         query(rc[x], rc[y], mid, tr, l, r);
}
bool query(int id1, int id2, int l, int r) {
  return query(rt[id1], rt[id2], 0, n, l, r);
}
}  // namespace SegTree
struct SuffixAutomaton {
  static const size_t AL = 26;
  int ch[N][AL], link[N], len[N], pos[N], lst, tot;
  SuffixAutomaton() : lst(1), tot(1) {}
  void add(int po, int c) {
    int cur = ++tot;
    len[cur] = len[lst] + 1;
    pos[cur] = po;
    int p = lst;
    for (; p && !ch[p][c]; p = link[p]) {
      ch[p][c] = cur;
    }
    if (!p) {
      link[cur] = 1;
    } else {
      int q = ch[p][c];
      if (len[p] + 1 == len[q]) {
        link[cur] = q;
      } else {
        int clone = ++tot;
        len[clone] = len[p] + 1;
        pos[clone] = pos[q];
        link[clone] = link[q];
        memcpy(ch[clone], ch[q], sizeof ch[q]);
        for (; p && ch[p][c] == q; p = link[p]) {
          ch[p][c] = clone;
        }
        link[cur] = link[q] = clone;
      }
    }
    lst = cur;
  }
  vector<int> get_ind() {
    static int cnt[N];
    for (int i = 1; i <= tot; ++i) {
      ++cnt[len[i]];
    }
    partial_sum(begin(cnt), end(cnt), cnt);
    vector<int> ret(tot);
    for (int i = 1; i <= tot; ++i) {
      ret[--cnt[len[i]]] = i;
    }
    return ret;
  }
} sam;
int dp[N], nxt[N], pa[N];
vector<int> ch[N];
int dfn[N], out[N];
void dfs1(int u) {
  static int tme = 0;
  dfn[u] = tme++;
  SegTree::add(tme - 1, tme, sam.pos[u]);
  for (int v : ch[u]) {
    dfs1(v);
  }
  out[u] = tme;
}
bool query(int u, int l, int r) { return SegTree::query(dfn[u], out[u], l, r); }
void dfs2(int u) {
  for (int v : ch[u]) {
    int w = nxt[u];
    if (query(w, sam.pos[v] - sam.len[v] + sam.len[w], sam.pos[v])) {
      dp[v] = dp[u] + 1;
      nxt[v] = v;
    } else {
      nxt[v] = w;
      dp[v] = dp[u];
    }
    dfs2(v);
  }
}
int main() {
  n = read();
  assert(n == reads(s));
  for (int i = 0; i < n; ++i) {
    sam.add(i, s[i]);
  }
  vector<int> vc = sam.get_ind();
  for (int x : vc) {
    ch[sam.link[x]].emplace_back(x);
  }
  dfs1(1);
  for (int u : ch[1]) {
    dp[u] = 1;
    nxt[u] = u;
    dfs2(u);
  }
  printf("%d\n", *max_element(dp + 1, dp + sam.tot + 1));
  return 0;
}
