import sys

read = sys.stdin.readline





def read_ints():

    return list(map(int, read().split()))





def read_a_int():

    return int(read())





def read_matrix(H):

    '''

    H is number of rows

    '''

    return [list(map(int, read().split())) for _ in range(H)]





def read_map(H):

    '''

    H is number of rows

    文字列で与えられた盤面を読み取る用

    '''

    return [read()[:-1] for _ in range(H)]





def read_tuple(H):

    '''

    H is number of rows

    '''

    ret = []

    for _ in range(H):

        ret.append(tuple(map(int, read().split())))

    return ret





def read_col(H, n_cols):

    '''

    H is number of rows

    n_cols is number of cols



    A列、B列が与えられるようなとき

    '''

    ret = [[] for _ in range(n_cols)]

    for _ in range(H):

        tmp = list(map(int, read().split()))

        for col in range(n_cols):

            ret[col].append(tmp[col])



    return ret





def index(a, x):

    'Locate the leftmost value exactly equal to x'

    i = bisect_left(a, x)



    if i != len(a) and a[i] == x:

        return i

    raise ValueError



# ポイント

# 文字列update

# 文字種の高速取得



# 文字種の高速取得について考えてみる

# 文字のidxを取得しておいて二分探索することで高速に取得することが可能





N = read_a_int()

S = list(read()[:-1])



from collections import defaultdict

from bisect import bisect_left, bisect_right, insort_left

char_idxs = defaultdict(lambda: [])

for i, s in enumerate(S):

    char_idxs[s].append(i)





def get_syurui(char_idxs, l, r):

    ret = 0

    for v in list(char_idxs.values()):

        l_idx = bisect_left(v, l)

        r_idx = bisect_right(v, r)

        # print(v,l_idx,r_idx)

        if r_idx - l_idx > 0:

            ret += 1

    return ret





Q = read_a_int()

for q in range(Q):

    com, a, b = read().split()

    if int(com) == 2:

        a, b = int(a) - 1, int(b) - 1

        print((get_syurui(char_idxs, a, b)))

    else:

        i = int(a) - 1

        if S[i] == b:

            continue

        # i文字目を消す

        tmp = char_idxs[S[i]]  # S[i]を更新しなきゃ

        del char_idxs[S[i]][index(tmp, i)]

        # cのidxに挿入し直す

        insort_left(char_idxs[b], i)

        # S[i]を更新しなきゃ

        S[i] = b

# https://atcoder.jp/contests/abc157/tasks/abc157_e

# セグメント木

# 各文字をビットに対応させる(セグ木を26本持っても良い)

import sys

read = sys.stdin.readline





def read_a_int():

    return int(read())





class SegmentTree:

    def __init__(self, ls: list, segfunc, identity_element):

        '''

        セグ木 (下書き)

        一次元のリストlsを受け取り初期化する。O(len(ls))

        区間のルールはsegfuncによって定義される

        identity elementは単位元。e.g., 最小値を求めたい→inf, 和→0, 積→1, gcd→0

        [単位元](https://ja.wikipedia.org/wiki/%E5%8D%98%E4%BD%8D%E5%85%83)

        '''

        self.ide = identity_element

        self.func = segfunc

        n = len(ls)

        self.num = 2 ** (n - 1).bit_length()  # n以上の最小の2のべき乗

        self.tree = [self.ide] * (2 * self.num - 1)  # −1はぴったりに作るためだけど気にしないでいい

        for i, l in enumerate(ls):  # 木の葉に代入

            self.tree[i + self.num - 1] = l

        for i in range(self.num - 2, -1, -1):  # 子を束ねて親を更新

            self.tree[i] = segfunc(self.tree[2 * i + 1], self.tree[2 * i + 2])



    def update(self, i, x):

        '''

        i番目の要素をxに変更する(木の中間ノードも更新する) O(logN)

        '''

        i += self.num - 1

        self.tree[i] = x

        while i:  # 木を更新

            i = (i - 1) // 2

            self.tree[i] = self.func(self.tree[i * 2 + 1],

                                     self.tree[i * 2 + 2])



    def query(self, l, r):

        '''

        区間[l,r)に対するクエリをO(logN)で処理する。例えばその区間の最小値、最大値、gcdなど

        '''

        if r <= l:

            return ValueError('invalid index (l,rがありえないよ)')

        l += self.num - 1

        r += self.num - 2  # ここで半開区間でなく[l,r]で以下を処理する

        res = self.ide

        while r - l > 1:  # 右から寄りながら結果を結合していくイメージ

            if l & 1 == 0:

                res = self.func(res, self.tree[l])

            if r & 1 == 1:

                res = self.func(res, self.tree[r])

                r -= 1

            l = l // 2  # 親の一つ右に移動

            r = (r - 1) // 2  # 親の一つ左に移動

        if l == r:

            res = self.func(res, self.tree[l])

        else:

            res = self.func(res, self.tree[l])

            res = self.func(res, self.tree[r])

        return res





def segfunc(x, y):

    # 処理したい内容

    return x | y





def moji_to_bit(a):

    return 1 << (ord(a) - ord('a'))





def bit_to_sum(n):

    return sum([(n >> i) & 1 for i in range(n.bit_length())])





N = read_a_int()

S = read()[:-1]

S_bit = [moji_to_bit(s) for s in S]



# build segment tree

st = SegmentTree(S_bit, segfunc, 0)



Q = read_a_int()

for q in range(Q):

    com, a, b = read().split()

    if int(com) == 1:

        i, c = int(a) - 1, b

        st.update(i, moji_to_bit(c))

    else:

        l, r = int(a) - 1, int(b)

        print((bit_to_sum(st.query(l, r))))

import sys

read = sys.stdin.readline


def read_ints():
    return list(map(int, read().split()))


def read_a_int():
    return int(read())


def read_matrix(H):
    """
    H is number of rows
    """
    return [list(map(int, read().split())) for _ in range(H)]


def read_map(H):
    """
    H is number of rows
    文字列で与えられた盤面を読み取る用
    """
    return [read()[:-1] for _ in range(H)]


def read_tuple(H):
    """
    H is number of rows
    """
    ret = []
    for _ in range(H):
        ret.append(tuple(map(int, read().split())))
    return ret


def read_col(H, n_cols):
    """
    H is number of rows
    n_cols is number of cols
    A列、B列が与えられるようなとき
    """
    ret = [[] for _ in range(n_cols)]
    for _ in range(H):
        tmp = list(map(int, read().split()))
        for col in range(n_cols):
            ret[col].append(tmp[col])
    return ret


def index(a, x):
    "Locate the leftmost value exactly equal to x"
    i = bisect_left(a, x)
    if i != len(a) and a[i] == x:
        return i
    raise ValueError


# ポイント
# 文字列update
# 文字種の高速取得
# 文字種の高速取得について考えてみる
# 文字のidxを取得しておいて二分探索することで高速に取得することが可能
N = read_a_int()
S = list(read()[:-1])
from collections import defaultdict
from bisect import bisect_left, bisect_right, insort_left

char_idxs = defaultdict(lambda: [])
for i, s in enumerate(S):
    char_idxs[s].append(i)


def get_syurui(char_idxs, l, r):
    ret = 0
    for v in list(char_idxs.values()):
        l_idx = bisect_left(v, l)
        r_idx = bisect_right(v, r)
        # print(v,l_idx,r_idx)
        if r_idx - l_idx > 0:
            ret += 1
    return ret


Q = read_a_int()
for q in range(Q):
    com, a, b = read().split()
    if int(com) == 2:
        a, b = int(a) - 1, int(b) - 1
        print((get_syurui(char_idxs, a, b)))
    else:
        i = int(a) - 1
        if S[i] == b:
            continue
        # i文字目を消す
        tmp = char_idxs[S[i]]  # S[i]を更新しなきゃ
        del char_idxs[S[i]][index(tmp, i)]
        # cのidxに挿入し直す
        insort_left(char_idxs[b], i)
        # S[i]を更新しなきゃ
        S[i] = b

# https://atcoder.jp/contests/abc157/tasks/abc157_e
# セグメント木
# 各文字をビットに対応させる(セグ木を26本持っても良い)
import sys

read = sys.stdin.readline


def read_a_int():
    return int(read())


class SegmentTree:
    def __init__(self, ls: list, segfunc, identity_element):
        """
        セグ木 (下書き)
        一次元のリストlsを受け取り初期化する。O(len(ls))
        区間のルールはsegfuncによって定義される
        identity elementは単位元。e.g., 最小値を求めたい→inf, 和→0, 積→1, gcd→0
        [単位元](https://ja.wikipedia.org/wiki/%E5%8D%98%E4%BD%8D%E5%85%83)
        """
        self.ide = identity_element
        self.func = segfunc
        n = len(ls)
        self.num = 2 ** (n - 1).bit_length()  # n以上の最小の2のべき乗
        self.tree = [self.ide] * (2 * self.num - 1)  # −1はぴったりに作るためだけど気にしないでいい
        for i, l in enumerate(ls):  # 木の葉に代入
            self.tree[i + self.num - 1] = l
        for i in range(self.num - 2, -1, -1):  # 子を束ねて親を更新
            self.tree[i] = segfunc(self.tree[2 * i + 1], self.tree[2 * i + 2])

    def update(self, i, x):
        """
        i番目の要素をxに変更する(木の中間ノードも更新する) O(logN)
        """
        i += self.num - 1
        self.tree[i] = x
        while i:  # 木を更新
            i = (i - 1) // 2
            self.tree[i] = self.func(self.tree[i * 2 + 1], self.tree[i * 2 + 2])

    def query(self, l, r):
        """
        区間[l,r)に対するクエリをO(logN)で処理する。例えばその区間の最小値、最大値、gcdなど
        """
        if r <= l:
            return ValueError("invalid index (l,rがありえないよ)")
        l += self.num - 1
        r += self.num - 2  # ここで半開区間でなく[l,r]で以下を処理する
        res = self.ide
        while r - l > 1:  # 右から寄りながら結果を結合していくイメージ
            if l & 1 == 0:
                res = self.func(res, self.tree[l])
            if r & 1 == 1:
                res = self.func(res, self.tree[r])
                r -= 1
            l = l // 2  # 親の一つ右に移動
            r = (r - 1) // 2  # 親の一つ左に移動
        if l == r:
            res = self.func(res, self.tree[l])
        else:
            res = self.func(res, self.tree[l])
            res = self.func(res, self.tree[r])
        return res


def segfunc(x, y):
    # 処理したい内容
    return x | y


def moji_to_bit(a):
    return 1 << (ord(a) - ord("a"))


def bit_to_sum(n):
    return sum([(n >> i) & 1 for i in range(n.bit_length())])


N = read_a_int()
S = read()[:-1]
S_bit = [moji_to_bit(s) for s in S]
# build segment tree
st = SegmentTree(S_bit, segfunc, 0)
Q = read_a_int()
for q in range(Q):
    com, a, b = read().split()
    if int(com) == 1:
        i, c = int(a) - 1, b
        st.update(i, moji_to_bit(c))
    else:
        l, r = int(a) - 1, int(b)
        print((bit_to_sum(st.query(l, r))))

+# https://atcoder.jp/contests/abc157/tasks/abc157_e +# セグメント木 +# 各文字をビットに対応させる(セグ木を26本持っても良い) - - -def read_ints(): -    return list(map(int, read().split())) -def read_matrix(H): -    """ -    H is number of rows -    """ -    return [list(map(int, read().split())) for _ in range(H)] +class SegmentTree: +    def __init__(self, ls: list, segfunc, identity_element): +        """ +        セグ木 (下書き) +        一次元のリストlsを受け取り初期化する。O(len(ls)) +        区間のルールはsegfuncによって定義される +        identity elementは単位元。e.g., 最小値を求めたい→inf, 和→0, 積→1, gcd→0 +        [単位元](https://ja.wikipedia.org/wiki/%E5%8D%98%E4%BD%8D%E5%85%83) +        """ +        self.ide = identity_element +        self.func = segfunc +        n = len(ls) +        self.num = 2 ** (n - 1).bit_length()  # n以上の最小の2のべき乗 +        self.tree = [self.ide] * (2 * self.num - 1)  # −1はぴったりに作るためだけど気にしないでいい +        for i, l in enumerate(ls):  # 木の葉に代入 +            self.tree[i + self.num - 1] = l +        for i in range(self.num - 2, -1, -1):  # 子を束ねて親を更新 +            self.tree[i] = segfunc(self.tree[2 * i + 1], self.tree[2 * i + 2]) + +    def update(self, i, x): +        """ +        i番目の要素をxに変更する(木の中間ノードも更新する) O(logN) +        """ +        i += self.num - 1 +        self.tree[i] = x +        while i:  # 木を更新 +            i = (i - 1) // 2 +            self.tree[i] = self.func(self.tree[i * 2 + 1], self.tree[i * 2 + 2]) + +    def query(self, l, r): +        """ +        区間[l,r)に対するクエリをO(logN)で処理する。例えばその区間の最小値、最大値、gcdなど +        """ +        if r <= l: +            return ValueError("invalid index (l,rがありえないよ)") +        l += self.num - 1 +        r += self.num - 2  # ここで半開区間でなく[l,r]で以下を処理する +        res = self.ide +        while r - l > 1:  # 右から寄りながら結果を結合していくイメージ +            if l & 1 == 0: +                res = self.func(res, self.tree[l]) +            if r & 1 == 1: +                res = self.func(res, self.tree[r]) +                r -= 1 +            l = l // 2  # 親の一つ右に移動 +            r = (r - 1) // 2  # 親の一つ左に移動 +        if l == r: +            res = self.func(res, self.tree[l]) +        else: +            res = self.func(res, self.tree[l]) +            res = self.func(res, self.tree[r]) +        return res -def read_map(H): -    """ -    H is number of rows -    文字列で与えられた盤面を読み取る用 -    """ -    return [read()[:-1] for _ in range(H)] +def segfunc(x, y): +    # 処理したい内容 +    return x | y -def read_tuple(H): -    """ -    H is number of rows -    """ -    ret = [] -    for _ in range(H): -        ret.append(tuple(map(int, read().split()))) -    return ret +def moji_to_bit(a): +    return 1 << (ord(a) - ord("a")) -def read_col(H, n_cols): -    """ -    H is number of rows -    n_cols is number of cols -    A列、B列が与えられるようなとき -    """ -    ret = [[] for _ in range(n_cols)] -    for _ in range(H): -        tmp = list(map(int, read().split())) -        for col in range(n_cols): -            ret[col].append(tmp[col]) -    return ret +def bit_to_sum(n): +    return sum([(n >> i) & 1 for i in range(n.bit_length())]) -def index(a, x): -    "Locate the leftmost value exactly equal to x" -    i = bisect_left(a, x) -    if i != len(a) and a[i] == x: -        return i -    raise ValueError - - -# ポイント -# 文字列update -# 文字種の高速取得 -# 文字種の高速取得について考えてみる -# 文字のidxを取得しておいて二分探索することで高速に取得することが可能 -S = list(read()[:-1]) -from collections import defaultdict -from bisect import bisect_left, bisect_right, insort_left - -char_idxs = defaultdict(lambda: []) -for i, s in enumerate(S): -    char_idxs[s].append(i) - - -def get_syurui(char_idxs, l, r): -    ret = 0 -    for v in list(char_idxs.values()): -        l_idx = bisect_left(v, l) -        r_idx = bisect_right(v, r) -        # print(v,l_idx,r_idx) -        if r_idx - l_idx > 0: -            ret += 1 -    return ret - - +S = read()[:-1] +S_bit = [moji_to_bit(s) for s in S] +# build segment tree +st = SegmentTree(S_bit, segfunc, 0) -    if int(com) == 2: -        a, b = int(a) - 1, int(b) - 1 -        print((get_syurui(char_idxs, a, b))) +    if int(com) == 1: +        i, c = int(a) - 1, b +        st.update(i, moji_to_bit(c)) -        i = int(a) - 1 -        if S[i] == b: -            continue -        # i文字目を消す -        tmp = char_idxs[S[i]]  # S[i]を更新しなきゃ -        del char_idxs[S[i]][index(tmp, i)] -        # cのidxに挿入し直す -        insort_left(char_idxs[b], i) -        # S[i]を更新しなきゃ -        S[i] = b +        l, r = int(a) - 1, int(b) +        print((bit_to_sum(st.query(l, r))))