// コメント, ロジックを修正して再提出.
// ※※※ 以下のライブラリを使用(一部改変) ※※※.
// 遅延評価セグメント木をソラで書きたいあなたに.
// http://tsutaj.hatenablog.com/entry/2017/03/30/224339
// http://judge.u-aizu.ac.jp/onlinejudge/review.jsp?rid=2236726
#include <bits/stdc++.h>
using namespace std;
#define repex(i, a, b, c) for(int i = a; i < b; i += c)
#define repx(i, a, b) repex(i, a, b, 1)
#define rep(i, n) repx(i, 0, n)
#define repr(i, a, b) for(int i = a; i >= b; i--)
#define pb push_back

struct LazySegmentTree{
private:
    int n;
    vector<int> node, lazy;
public:
    LazySegmentTree(vector<int> v){
        int sz = (int)v.size();
        n = 1; while(n < sz) n *= 2;
        node.resize(2 * n - 1);
        lazy.resize(2 * n - 1, 0);
        // 元配列 の 値 を, セグメント木のノード に 保存.
        rep(i, sz) node[i + n - 1] = v[i];
        repr(i, n - 2, 0) node[i] = node[i * 2 + 1] + node[i * 2 + 2];
    }
    void eval(int k, int l, int r){
        if(lazy[k] != 0) {
            node[k] += lazy[k];
            if(r - l > 1) {
                lazy[2 * k + 1] += lazy[k] / 2;
                lazy[2 * k + 2] += lazy[k] / 2;
            }
            lazy[k] = 0;
        }
    }
    void update(int a, int b, int x, int k = 0, int l = 0, int r = -1){
        if(r < 0) r = n;
        eval(k, l, r);
        if(b <= l || r <= a) return;
        if(a <= l && r <= b){
            lazy[k] += (r - l) * x;
            eval(k, l, r);
        }else{
            update(a, b, x, 2 * k + 1,           l, (l + r) / 2);
            update(a, b, x, 2 * k + 2, (l + r) / 2,           r);
            node[k] = max(node[2 * k + 1], node[2 * k + 2]);
        }
    }
    int query(int a, int b, int k = 0, int l = 0, int r = -1){
        if(r < 0) r = n;
        eval(k, l, r);
        if(b <= l || r <= a) return 0;
        if(a <= l && r <= b) return node[k];
        int vl = query(a, b, 2 * k + 1,           l, (l + r) / 2);
        int vr = query(a, b, 2 * k + 2, (l + r) / 2,           r);
        return max(vl, vr);
    }

};

int main(){
    
    // 1. 入力情報.
    int N, Q;
    char c[505050];
    scanf("%d %s %d", &N, c, &Q);
    
    // 2. 区間更新(遅延伝播セグメント木).
    LazySegmentTree seg(vector<int>(505050, 0));
    vector<LazySegmentTree> vSeg;
    rep(i, 26) vSeg.pb(seg);
    rep(j, N) vSeg[c[j] - 'a'].update(j, j + 1, 1);
    // rep(i, 26){
    //     rep(j, N) printf("%d ", vSeg[i].query(j, j + 1));
    //     puts("");
    // }
    
    // 3. 各クエリに回答.
    int n, iq, lq, rq, bef, cur, ans, bCnt, cCnt;
    char cq[5];
    rep(i, Q){
        scanf("%d", &n);
        if(n == 1){
            scanf("%d %s", &iq, cq);
            iq--;
            
            // 変更前の文字.
            bef = c[iq] - 'a';
            
            // 変更後の文字.
            cur = cq[0] - 'a';
            
            // 変更前文字の出現個数.
            bCnt = vSeg[bef].query(iq, iq + 1);
            
            // 変更前文字を減算.
            if(bCnt > 0) vSeg[bef].update(iq, iq + 1, -1);
            
            // 変更後文字の出現個数.
            cCnt = vSeg[cur].query(iq, iq + 1);
            
            // 変更後文字を加算.
            if(cCnt == 0) vSeg[cur].update(iq, iq + 1, 1);
            
            // 文字列を, 変更後文字 に 更新.
            c[iq] = cq[0];
        }else{
            scanf("%d %d", &lq, &rq);
            lq--, ans = 0;
            
            // 部分文字列(区間[lq, rq]) の 種類数は？
            rep(j, 26) ans += vSeg[j].query(lq, rq);
            
            // 出力.
            printf("%d\n", ans);
        }
    }
    return 0;
    
}

Measured memory_bytes: 222152