Harryxun/sft_time huggingface dataset:
### BUGGY CODE ###

n = int(input())
s = list(input())
q = int(input())

from collections import Counter
ans = []
for _ in range(q):
    a, b, c = input().split()
    if a == '1':
        s[int(b)-1] = c
    else:
        dic = Counter(s[int(b)-1:int(c)])
        ans.append(len(dic))

for i in ans:
    print(i)

### FIXED CODE ###

# Reference: https://qiita.com/dn6049949/items/afa12d5d079f518de368

# self.data: 1-indexed
#    __1__
#  _2_   _3_
# 4   5 6   7

# f(f(a, b), c) == f(a, f(b, c))

class SegmentTree:
    # a = [default] * n
    # O(n)
    def __init__(self, n, f=max, default=-2**30):
        self.num_leaf = 2 ** (n-1).bit_length()
        self.data = [default] * (2*self.num_leaf)
        self.f = f

    # a[i] = x
    # O(log(n))
    def update(self, i, x):
        i += self.num_leaf
        self.data[i] = x
        i >>= 1
        while i > 0:
            self.data[i] = self.f(self.data[2*i], self.data[2*i+1])
            i >>= 1

    # return f(a[l:r])
    # O(log(n))
    def query(self, l, r):
        l += self.num_leaf
        r += self.num_leaf - 1
        lres, rres = self.data[0], self.data[0] # self.data[0] == default
        while l < r:
            if l & 1:
                lres = self.f(lres, self.data[l])
                l += 1
            if not r & 1:
                rres = self.f(self.data[r], rres)
                r -= 1
            l >>= 1
            r >>= 1
        if l == r:
            res = self.f(self.f(lres, self.data[l]), rres)
        else:
            res = self.f(lres, rres)
        return res

    # You can use min_index only if f == max.
    # return min({i | x <= i and v <= a[i]}, self.num_leaf)
    # O(log(n))
    def min_index(self, x, v):
        x += self.num_leaf
        while self.data[x] < v:
            if x & 1:
                if x.bit_length() == (x+1).bit_length():
                    x += 1
                else:
                    return self.num_leaf
            else:
                x >>= 1
        while x < self.num_leaf:
            if self.data[2*x] >= v:
                x = 2*x
            else:
                x = 2*x + 1
        return x - self.num_leaf

from sys import stdin
def input():
    return stdin.readline().strip()

def main():
    n = int(input())
    s = list(input())

    forest = [SegmentTree(n, f=max, default=0) for _ in range(26)]
    for ind, si in enumerate(s):
        forest[ord(si) - 97].update(ind, 1)

    q = int(input())
    ans = []
    for _ in range(q):
        typ, l, r = input().split()
        l = int(l) - 1

        if typ == '1':
            forest[ord(s[l]) - 97].update(l, 0)
            s[l] = r
            forest[ord(r) - 97].update(l, 1)

        else:
            cnt = 0
            for tree in forest:
                if tree.query(l, int(r)) == 1:
                    cnt += 1
            ans.append(cnt)

    for i in ans:
        print(i)

main()
### BUGGY CODE ###

N, K = map(int, input().split())
P = list(map(int, input().split()))
C = list(map(int, input().split()))

ans = -float('inf')
for s in range(N):
    ### Step1
    S = []  # 累積和S. ただし, 初項は0ではなく1回目の移動後の得点とする.
    # 1回目の移動
    i = P[s] - 1
    S.append(C[i])
    # 2回目以降の移動. スタート地点に戻ってくるまで繰り返し.
    while i != s:
        i = P[i] - 1
        S.append(S[-1] + C[i])

    ### Step2: KとSの長に応じて場合分けして, 得点の最大値を求める.
    # 1周未満しか移動できない場合:
    if K <= len(S):
        score = max(S[:K])
    # 1周以上回ることができるが, ループを1周したときに得点が減る場合:
    elif S[-1] <= 0:
        score = max(S)
    # 1週以上回ることができ, かつループを1週するごとに得点が増える場合:
    else:
        # ループを (K // len(S) - 1)回 廻る場合:
        score1 = S[-1] * (K // len(S) - 1)
        score1 += max(S)
        # ループを (K // len(S))回 廻る場合:
        score2 = S[-1] * (K // len(S))
        r = K % len(S)
        if r != 0:
            score2 += max(0, max(S[:r]))
        # score1 と score2 の大きい方がこの場合の得点の最大値
        score = max(score1, score2)

    ans = max(ans, score)

print(ans)

### FIXED CODE ###

N, K = map(int, input().split())
P = list(map(int, input().split()))
C = list(map(int, input().split()))

ans = -float('inf')
for s in range(N):
    ### Step1
    S = []  # 累積和S. ただし, 初項は0ではなく1回目の移動後の得点とする.
    # 1回目の移動
    i = P[s] - 1
    S.append(C[i])
    # 2回目以降の移動. スタート地点に戻ってくるまで繰り返し.
    while i != s:
        i = P[i] - 1
        S.append(S[-1] + C[i])

    ### Step2: KとSの長に応じて場合分けして, 得点の最大値を求める.
    # 1周未満しか移動できない場合:
    if K <= len(S):
        score = max(S[:K])
    # 1周以上回ることができるが, ループを1周したときに得点が減る場合:
    elif S[-1] <= 0:
        score = max(S)
    # 1週以上回ることができ, かつループを1週するごとに得点が増える場合:
    else:
        # ループを (K // len(S) - 1)回 廻る場合:
        score1 = S[-1] * (K // len(S) - 1)
        score1 += max(S)
        # ループを (K // len(S))回 廻る場合:
        score2 = S[-1] * (K // len(S))
        r = K % len(S)
        if r != 0:
            score2 += max(0, max(S[:r]))
        # score1 と score2 の大きい方がこの場合の得点の最大値
        score = max(score1, score2)

    ans = max(ans, score)

print(ans)
### BUGGY CODE ###

mtx = [[0] * 2019 for _ in range(2019)]

for i in range(len(mtx)):
    for j in range(len(mtx)):
        mtx[i][j] = (i * j) % 2019

l, r = map(int, input().split())

min_val = 10000000

for i in range(l % 4040, r+1 % 4040):
    for j in range(i+1 % 4040, r+1 % 4040):
        a = i % 2019
        b = j % 2019
        ret = mtx[a][b]
        min_val = min(min_val, ret)

print(min_val)



### FIXED CODE ###

mtx = [[0] * 2019 for _ in range(2019)]

for i in range(len(mtx)):
    for j in range(len(mtx)):
        mtx[i][j] = (i * j) % 2019

l, r = map(int, input().split())

min_val = 10000000

l_mod = l % 2019
r_mod = r % 2019

if r_mod < l_mod:
    r_mod += 2019

for i in range(l, r):
    for j in range(i+1 , r + 1):
        a = i % 2019
        b = j % 2019
        ret = mtx[a][b]
        min_val = min(min_val, ret)
        if min_val == 0:
            print(0)
            exit()

print(min_val)
### BUGGY CODE ###

n = int(input())
for m in range(1, n+1):
    cnt = 0
    for i in range(1, int(n**0.5)+2):
        a = n - i**2
        if a >= 1:
            for j in range(1, int(a**0.5) + 2):
                b = n - i**2 - j**2
                if b >= 1:
                    for k in range(1, int(b**0.5) + 2):
                        if i**2 + j**2 + k**2 + i*j + j*k + k*i == m:
                            cnt += 1
    print(cnt)


### FIXED CODE ###

n = 10005
f = [0]*10005
for i in range(1, int(n**0.5)):
    a = n - i**2
    if a >= 1:
        for j in range(1, int(a**0.5)):
            b = n - i**2 - j**2
            if b >= 1:
                for k in range(1, int(b**0.5)):
                    c = i**2 + j**2 + k**2 + i*j + j*k + k*i
                    if c <= 10001:
                        f[c] += 1

n = int(input())
for i in range(1, n+1):
    print(f[i])
### BUGGY CODE ###

N = int(input())
S = list(input())
for i in range(N):
  if S[i] == 'W':
    S[i] = 1
  else:
    S[i] = 0
ans = []
for i in range(N):
  W = sum(S[0:i])
  E = len(S[i+1:N+1]) - sum(S[i+1:N+1])
  ans.append(W+E)
print(min(ans)) 

### FIXED CODE ###

N = int(input())
S = list(input())
E = S.count('E')
ans = []
prev = E
pS = 'E'
for i in range(N):
  if S[i] == 'W':
    current = prev
  elif S[i] == 'E':
    current = prev - 1
  if pS == 'W':
    current += 1
  ans.append(current)
  prev = current
  pS = S[i]
print(min(ans))
### BUGGY CODE ###

a,b,c,d=map(int,input().split())
print(len([1 for i in range(a,b+1) if i%c and i%d]))

### FIXED CODE ###

a,b,c,d=map(int,input().split())
def gcd(x,y):
  if x==0:
    return y
  else:return gcd(y%x,x)

def f(a,s,d):
  x=s*d//gcd(s,d)
  return a-a//s-a//d+a//x

print(f(b,c,d)-f(a-1,c,d))
### BUGGY CODE ###

# dp[i][j]：i枚で表がj枚になる確率
# sum(dp[N][(N+1)//2:])を求めればいいため，dp[N][:(N+1)//2]は求める必要が無い
import sys
sys.setrecursionlimit(10 ** 9)

def rec(i,j):
  if dp[i][j] != -1:
    return dp[i][j]
  else:
    dp[i][j] = rec(i-1,j-1) * P[i-1] + rec(i-1,j) * (1-P[i-1]) if j > 0 else rec(i-1,j) * (1-P[i-1])
    return dp[i][j]

N = int(input())
P = list(map(float, input().split()))

#dp = [[0]*(N+1)] + [[-1]*(N+1) for _ in range(N)]
dp = [[-1]*(N+1) for _ in range(N+1)]
dp[0][0] = 1
for i in range(N):
  dp[i][i+1] = 0

print(sum([rec(N, i) for i in range((N+1)//2, N+1)]))

### FIXED CODE ###

# dp[i][j]：i枚で表がj枚になる確率
# 1 - sum(:dp[N][(N+1)//2])を求めればいいため，dp[N][(N+1)//2:]は求める必要が無い
# jが大きい順に更新すればdp[i][j]のjを保持する必要はない

N = int(input())
P = list(map(float, input().split()))
overhalf_N = (N+1)//2
underhalf_N = overhalf_N - 1

dp = [0]*(N+1) # dp[N][j]：N枚で表がj枚になる確率

dp[0] = 1
for i in range(overhalf_N):
  pc = 1 - P[i]
  for j in range(i+1, 0, -1):
    dp[j] = dp[j-1] * P[i] + dp[j] * pc
  dp[0] = dp[0] * pc

for i in range(overhalf_N,N):
  pc = 1 - P[i]
  for j in range(underhalf_N, 0, -1):
    dp[j] = dp[j-1] * P[i] + dp[j] * pc
  dp[0] = dp[0] * pc

print(1-sum(dp[:overhalf_N]))
### BUGGY CODE ###

n, m = map(int, input().split())
lr = [list(map(int, input().split())) for i in range(m)]
ans = 0
for i in range(1, n+1):
  b = True
  for j in range(m):
    if i < lr[j][0] or lr[j][1] < i:
      b = False
  if b:
    ans += 1
print(ans)

### FIXED CODE ###

n, m = map(int, input().split())
lr = [list(map(int, input().split())) for i in range(m)]
l = []
r = []
for i in range(m):
  l.append(lr[i][0])
  r.append(lr[i][1])
lm = max(l)
rm = min(r)
ans = 0
for i in range(1, n+1):
  if lm <= i <= rm:
    ans += 1
print(ans)
### BUGGY CODE ###

S = input()

s_list=[]
for i in range(len(S)):
    s_list.append(S[i])

num=0
judge=1
N=len(s_list)
while judge==1:
    judge=0
    for i in range(N):
        if i+1 < len(s_list):
            if s_list[i] != s_list[i+1]:
                #print(i,len(s_list),s_list,s_list[i],s_list[i+1])
                del s_list[i:i+2]
                num+=2
                judge=1
        else:
            break
    N = len(s_list)
print(num)

### FIXED CODE ###

S = input()

zero = 0
one = 0
for i in range(len(S)):
	if S[i] == '0':
		zero += 1
	else:
		one += 1
print(min(zero,one)*2)
### BUGGY CODE ###

a,b=map(int,input().split())
n=0
result='IMPOSSIBLE'
while True:
    if abs(a-n)==abs(b-n):
        result=n
        break
    n+=1
print(result)

### FIXED CODE ###

a,b=map(int,input().split())
n=0
result='IMPOSSIBLE'
if (a+b)%2==0:
    result=(a+b)//2
print(result)
### BUGGY CODE ###

enemies = [int(input())]

def attack(N):
    return [N // 2] * 2

times = 0
while len(enemies) > 0:
    tmp = enemies.pop(-1)
    times += 1
    if tmp >= 2:
        enemies += attack(tmp)

print(times)

### FIXED CODE ###

enemie = int(input())

times = 0
for i in range(enemie):
    times += 1
    if enemie >= 2:
        enemie //= 2
    else:
        break

ans = 0
for j in range(times):
    ans += 2**j
    
print(ans)
### BUGGY CODE ###

import math
from functools import reduce

def gcd(*numbers):
    return reduce(math.gcd, numbers)

import sys
K=input()
K=int(K)
L=[]
a=1
b=1
c=1
for i in range(K):
    for j in range(K):
        for m in range(K):
            L.append(gcd(a,b,c))
            c=c+1
        c=1
        b=b+1
    b=1
    c=1
    a=a+1

S=sum(L)
print(S)

### FIXED CODE ###

import math
from functools import reduce

def gcd(*numbers):
    return reduce(math.gcd, numbers)

import sys
K=input()
K=int(K)
S=0
a=1
b=1
c=1
for i in range(K):
    for j in range(K):
        if gcd(a,b)==1:
            S+=K
        else:
            S+=sum([gcd(a,b,c) for c in range(1,K+1)])
        c=1
        b=b+1
    b=1
    c=1
    a=a+1

print(S)
### BUGGY CODE ###

import sys
input = sys.stdin.readline

H,W,D = map(int,input().split())
A = [list(map(int,input().split())) for i in range(H)]
Q = int(input())
LR = [list(map(int,input().split())) for i in range(Q)]

Point = {}
for y in range(H) :
    for x in range(W) :
        Point[A[y][x]] = (y, x)

def dist (y1,x1,y2,x2) :
    return abs(y1-y2) + abs(x1-x2)

for i in range(Q) :
    res = 0
    l,r = LR[i][0], LR[i][1]
    while l != r :
        l += D
        res += dist(Point[l][0], Point[l][1], Point[l-D][0], Point[l-D][1])
    print(res)


### FIXED CODE ###

H,W,D = map(int,input().split())
Point = {}
for y in range(H) :
    line = list(map(int,input().split()))
    for x,p in enumerate(line) :
        Point[p] = (y,x)

Q = int(input())

def dist(ny,nx,py,px) :
    return abs(ny-py) + abs(nx-px)

Cost = [0]*(H*W+1)
for i in range(D+1, H*W+1) :
    yi,xi = Point[i-D]
    yj,xj = Point[i]
    Cost[i] = Cost[i-D] + dist(yi,xi,yj,xj)


for i in range(Q) :
    l,r = map(int,input().split())
    print(Cost[r] - Cost[l])
### BUGGY CODE ###

import sys
from collections import Counter
import random
import numpy as np
import time

n=int(input())
a=list(map(int,input().split()))
S=[sum(a[:i])for i in range(n+1)]
m=n+1
c=0
for i in range(1,m):
    for j in range(i):
        if(S[i]==S[j]):c+=1
print(c)

### FIXED CODE ###

import sys
from collections import Counter
import random
import numpy as np
import time

n=int(input())
a=list(map(int,input().split()))
S=[0]
for i in range(n):
    S.append(S[i]+a[i])
    
S.sort()
c=0
ch=0
for i in range(len(S)-1):
    if(S[i]!=S[i+1]):
        ch+=1
        c+=(ch*(ch-1))//2
        ch=0
    else:ch+=1
if(ch!=0):
    ch+=1
    c+=(ch*(ch-1))//2
print(c)
### BUGGY CODE ###

from bisect import bisect_right as br
import random
def calc1(N, A, B):
    s, t = 0, 0
    for i in range(N):
        s ^= A[i]
        t ^= B[i]
    ans = (s ^ t) * (N % 2)
    for k in range(1, 29):
        m = (1<<k) - 1
        AA = sorted([a & m for a in A])
        BB = sorted([a & m for a in B])
        ans ^= (sum([N - br(AA, (1<<k) - b - 1) for b in BB]) % 2) << k
        # print(k, sum([N - br(AA, (1<<k) - b - 1) for b in BB]))
    return ans
def calc2(N, A, B):
    s, t = 0, 0
    for i in range(N):
        s ^= A[i]
        t ^= B[i]
    ans = (s ^ t) * (N % 2)
    for k in range(1, 29):
        m = (1<<k) - 1
        AA = sorted([a & m for a in A])
        BB = sorted([a & m for a in B])[::-1]
        m += 1
        i, j = 0, 0
        while i < N and AA[i] + BB[j] < m:
            i += 1
        s = 0
        while i < N:
            while j < N and AA[i] + BB[j] >= m:
                j += 1
            s += j
            i += 1
        # print("k, s =", k, s)
        ans += (s % 2) << k
    return ans
N = int(input())
A = [int(a) for a in input().split()]
B = [int(a) for a in input().split()]
print(calc1(N, A, B))

### FIXED CODE ###

from bisect import bisect_right as br
N = int(input())
A = [int(a) for a in input().split()]
B = [int(a) for a in input().split()]
s, t = 0, 0
for i in range(N):
    s ^= A[i]
    t ^= B[i]
ans = (s ^ t) * (N % 2)
for k in range(1, 29):
    m = (1<<k) - 1
    AA = sorted([a & m for a in A])
    BB = sorted([a & m for a in B])[::-1]
    m += 1
    i, j = 0, 0
    while i < N and AA[i] + BB[j] < m:
        i += 1
    s = 0
    while i < N:
        while j < N and AA[i] + BB[j] >= m:
            j += 1
        s += j
        i += 1
    ans ^= (s % 2) << k
print(ans)
### BUGGY CODE ###

import sys
# input = sys.stdin.readline()
N = int(input())
A = list(map(int,input().split()))
sum_r = 0
# print(A[0::2])
# print(A[1::2])
sum_odd = 0
sum_even = 0
for i in range(N):
    a = A[i]
    sum_r += a
    if i % 2 == 0:
        sum_odd += a
    else:
        sum_even += a

ans = ''
rolling_even = sum_even 
rolling_odd = sum_odd - A[0]
#print(sum_r,sum_even)
ans += str(sum_r - 2 * rolling_even) + ' ' + str(sum_r - 2 * rolling_odd)
sys.stdout.write(ans + ' ')
mepo = A[1]
mope = A[0]
for i in range(2,N):
    tmp = sum_r

    if i % 2 == 0:
        # rolling_even += A[(i-2)%N] - A[(i-1)%N]
        mepo = A[(i-1)%N]
        rolling_even += mope - mepo
        tmp = sum_r - 2 * rolling_even
    else:
        mope = A[(i-1)%N]
        rolling_odd += mepo - mope
        tmp = sum_r - 2 * rolling_odd
    sys.stdout.write(' '+str(tmp))
    ans += ' ' + str(tmp)
sys.stdout.write('\n')




### FIXED CODE ###

import sys
# input = sys.stdin.readline()
N = int(input())
A = list(map(int,input().split()))
sum_r = 0
# print(A[0::2])
# print(A[1::2])
sum_odd = 0
sum_even = 0
for i in range(N):
    a = A[i]
    sum_r += a
    if i % 2 == 0:
        sum_odd += a
    else:
        sum_even += a

ans = ''
rolling_even = sum_even 
rolling_odd = sum_odd - A[0]
#print(sum_r,sum_even)
X_i = sum(A) - 2 * sum(A[1::2])
sys.stdout.write(str(X_i))
for i in range(N-1):
    X_i_plus_1 = 2 * A[i] - X_i
    sys.stdout.write(' ' + str(X_i_plus_1))
    X_i = X_i_plus_1
# ans += str(sum_r - 2 * rolling_even) + ' ' + str(sum_r - 2 * rolling_odd)
# sys.stdout.write(ans + ' ')
# mepo = A[1]
# mope = A[0]
# for i in range(2,N):
#     tmp = sum_r

#     if i % 2 == 0:
#         # rolling_even += A[(i-2)%N] - A[(i-1)%N]
#         mepo = A[(i-1)]
#         rolling_even += mope - mepo
#         tmp = sum_r - 2 * rolling_even
#     else:
#         mope = A[(i-1)]
#         rolling_odd += mepo - mope
#         tmp = sum_r - 2 * rolling_odd
#     sys.stdout.write(' '+str(tmp))
#     ans += ' ' + str(tmp)
# sys.stdout.write('\n')
### BUGGY CODE ###

import os
import sys

import numpy as np

if os.getenv("LOCAL"):
    sys.stdin = open("_in.txt", "r")

sys.setrecursionlimit(10 ** 9)
INF = float("inf")
IINF = 10 ** 18
MOD = 10 ** 9 + 7
# MOD = 998244353

# 解説AC
# http://drken1215.hatenablog.com/entry/2019/09/16/213700
N = int(sys.stdin.readline())
A = list(map(int, sys.stdin.readline().split()))

DIGIT = 60

mat = []
for a in A:
    mat.append(list(np.binary_repr(a, DIGIT)))
mat = np.array(mat, dtype=np.uint8).T

zero_rows = mat.sum(axis=1) == 0
# ビットの数が奇数の場合、どう頑張っても変わらない
odd_rows = (mat.sum(axis=1) % 2).astype(bool)
mat[odd_rows, :] = 0

# MOD 2 の方程式
# 全部 1 にしたい
mat = np.hstack((mat, np.ones((DIGIT, 1), dtype=np.uint8)))

# 掃き出し法
used_row = np.zeros(DIGIT, dtype=bool)
for col in range(N):
    pivot = (mat[:, col] & ~used_row).argmax()
    if not mat[pivot, col]:
        continue
    used_row[pivot] = True

    pivot_row = mat[pivot].copy()
    mat[mat[:, col].astype(bool)] ^= pivot_row
    mat[pivot] = pivot_row

# 矛盾するやつを除外
# 一番右だけ 1 で他が 0 の行は矛盾
ng_rows = (mat[:, :-1].sum(axis=1) == 0) & (mat[:, -1] == 1)
ok_even_rows = ~odd_rows & ~zero_rows & ~ng_rows

ans = 0
for d in range(DIGIT):
    ans += pow(2, d) * (odd_rows[~d] + ok_even_rows[~d] * 2)
print(ans)


### FIXED CODE ###

import os
import os
import sys
from functools import reduce
from operator import xor

import numpy as np

if os.getenv("LOCAL"):
    sys.stdin = open("_in.txt", "r")

sys.setrecursionlimit(2147483647)
INF = float("inf")
IINF = 10 ** 18
MOD = 10 ** 9 + 7

N = int(sys.stdin.readline())
A = list(map(int, sys.stdin.readline().split()))
A = np.array(A, dtype=np.int64)


def dump(arr):
    for a in arr:
        print(np.binary_repr(a, 60))
    print()


# 解説AC
# 立ってるビットが奇数ならどう分けても結果は同じ
odds = reduce(xor, A)
ans = odds

# 奇数のやつは無視
A &= ~odds

# 行標準形にする
# https://qiita.com/vain0x/items/015ff6d49853e5d9d403#f---xor-sum-3
rank = 0
for d in reversed(range(60)):
    # 下からd桁目が立ってる最初のindex
    bits1 = (A >> d & 1).astype(bool)
    pos = bits1[rank:].argmax() + rank
    if not bits1[pos]:
        continue  # 全部ゼロ
    if pos < rank:
        continue  # rank より下に 1 がない。どう頑張っても変えられない。(?)

    # 基準にする行
    pivot_row = A[pos]
    # 基準以外を全部ゼロにする
    A[bits1] ^= pivot_row
    A[pos] = pivot_row
    # 基準行を一番上に持ってくる
    A[pos], A[rank] = A[rank], A[pos]
    rank += 1

ans += reduce(xor, A) * 2
print(ans)
### BUGGY CODE ###

from collections import deque

def isCLKWISE(ph):
    a = [ph[-1][0]-ph[-3][0],ph[-1][1]-ph[-3][1]]
    b = [ph[-2][0]-ph[-3][0],ph[-2][1]-ph[-3][1]]
    crs = a[0]*b[1] - a[1]*b[0]
    if crs < 0 :
        return False
    else :
        return True
    
def ConvexHullScan(P):
    P = sorted(P)
    phU = [P[0],P[1]]
    for p in P[2:]:
        phU.append(p)
        while(True):
            if isCLKWISE(phU) :
                break
            else :
                del phU[-2]
                if len(phU) == 2 :
                    break
    phL = [P[-1],P[-2]]
    for p in P[-3::-1]:
        phL.append(p)
        while(True):
            if isCLKWISE(phL) :
                break
            else :
                del phL[-2]
                if len(phL) == 2 :
                    break
    ph = phU + phL[1:-1]
    return ph

n = int(input())
P = deque([])
for i in range(n):
    P.append([int(x) for x in input().split()])
    
Q = ConvexHullScan(P)
Q.reverse()
print(len(Q))
for q in Q:
    idx = min([[x[1][1],x[1][0],x[0]] for x in enumerate(Q)])[2]
    R = Q[idx:] + Q[:idx]
for r in R:
    print(r[0],r[1])

### FIXED CODE ###

def isCLKWISE(ph) : return not ((ph[-1][0] - ph[-3][0])*(- ph[-3][1] + ph[-2][1]) - (ph[-2][0] - ph[-3][0])*(- ph[-3][1] + ph[-1][1] ) < 0)
      
def ConvexHullScan(P):
    P = sorted(P)
    phU = [P[0],P[1]]
    for p in P[2:]:
        phU.append(p)
        while(True):
            try :
                if isCLKWISE(phU) : break
                else : del phU[-2]
            except IndexError : break
    phL = [P[-1],P[-2]]
    for p in P[-3::-1]:
        phL.append(p)
        while(True):
            try :
                if isCLKWISE(phL) : break
                else : del phL[-2]
            except IndexError : break
    ph = phU + phL[1:-1]
    return ph
  
P = [[int(x) for x in input().split()] for _ in range(int(input()))]
P = ConvexHullScan(P)
P.reverse()
print(len(P))
idx = min([[x[1][1],x[1][0],x[0]] for x in enumerate(P)])[2]
for p in P[idx:] + P[:idx] : print(p[0],p[1])
### BUGGY CODE ###

class LcpArr(object):
    def __init__(self, orig_str: str):
        self.orig_str = orig_str
        self.sufs = self.__create_sufs()
        self.suf_arr, self.lcp_arr = self.__create_arr()

    def __call__(self, i: int, j: int):
        si, sj = sorted([self.suf_arr.index(i), self.suf_arr.index(j)])
        return min(self.lcp_arr[si:sj])

    def __create_sufs(self):
        n = len(self.orig_str)
        return [self.orig_str[i:] for i in range(n)] + [""]

    def __create_arr(self):
        n = len(self.orig_str)
        suf_arr = sorted(range(n), key=lambda x: self.sufs[x]) + [n]

        lcp_arr, c = [-1]*n, 0
        for i in range(n):
            si = suf_arr.index(i)
            lcp_arr[si] = c = self.__lcp_len(i, suf_arr[si+1], c)

        return suf_arr, lcp_arr

    def __lcp_len(self, i: int, j: int, c: int):
        suf, next_suf = self.sufs[i], self.sufs[j]

        l = c-1 if c>=2 else 0
        for i in range(l, min(len(suf), len(next_suf))):
            if suf[i] != next_suf[i]:
                break
            l += 1
        return l

def f_e():
    n = int(input())
    s = input()
    lcp = LcpArr(s)
    a = 0
    for i in range(n):
        for j in range(n):
            if i>=j:
                continue
            a = max(a, min(lcp(i, j), j-i))
    print(a)

if __name__ == "__main__":
    f_e()


### FIXED CODE ###

def f_e_dp():
    n = int(input())
    s = input()
    dp = [[0]*(n+1) for _ in range(n+1)]
    for i in reversed(range(n)):
        for j in reversed(range(n)):
            if s[i]==s[j] and i<j:
                dp[i][j] = dp[i+1][j+1]+1
    a = 0
    for i in range(n):
        for j in range(n):
            if i<j:
                a = max(a, min(j-i, dp[i][j]))
    print(a)

if __name__ == "__main__":
    f_e_dp()