#include<bits/stdc++.h>
#include<iostream>
#include<algorithm>
#include<vector>
#include<stdio.h>
#include<cstring>
#include<math.h>
#include<map>
#include<bitset>
#include<iomanip>
#include<queue>
#include<functional>
#include<stack>
#include<tuple>
#include<cassert>
using namespace std;

#define rep(i, n) for (int i = 0; i < (int)(n); ++i)
using ll = long long;
using P = pair<int, int>;
using MATRIX = vector< vector<ll> >;
template <class T> inline bool chmin(T &x, const T &y) {if (x > y) {x = y; return 1;} return 0;}
template <class T> inline bool chmax(T &x, const T &y) {if (x < y) {x = y; return 1;} return 0;}

const int INF = 1000000000;

/* RSQ：[0,n-1] について、区間ごとの合計を管理する構造体
    update(i,x): i 番目の要素を x に更新。O(log(n))
    query(a,b): [a,b) での合計を取得。O(log(n))
*/
template <typename T>
struct RSQ {
    //const T INF = numeric_limits<T>::max();
    int n;         // 葉の数
    vector<T> dat, lazy; // 完全二分木の配列
    RSQ(int n_) : n(), dat(n_ * 4, 0), lazy(n_*4, 0) { // 葉の数は 2^x の形
        int x = 1;
        while (n_ > x) {
            x *= 2;
        }
        n = x;
    }
 
    void update(int i, T x) {
        i += n - 1;
        dat[i] = x;
        while (i > 0) {
            i = (i - 1) / 2;  // parent
            dat[i] = dat[i * 2 + 1] + dat[i * 2 + 2];
        }
    }
 
    // the minimum element of [a,b)
    T query(int a, int b) { return query_sub(a, b, 0, 0, n); }
    T query_sub(int a, int b, int k, int l, int r) {
        eval(k, l, r);
        if (r <= a || b <= l) {
            return 0;
        } else if (a <= l && r <= b) {
            return dat[k];
        } else {
            T vl = query_sub(a, b, k * 2 + 1, l, (l + r) / 2);
            T vr = query_sub(a, b, k * 2 + 2, (l + r) / 2, r);
            return vl + vr;
        }
    }

    void eval(int k, int l, int r) {
        if (lazy[k] == 0) return;  // 更新するものが無ければ終了
        if (k < n - 1) {             // 葉でなければ子に伝搬
            lazy[k * 2 + 1] = lazy[k];
            lazy[k * 2 + 2] = lazy[k];
        }
        // 自身を更新
        dat[k] += lazy[k] * (r - l);
        lazy[k] = 0;
    }
    void update(int a, int b, T x, int k, int l, int r) {
        eval(k, l, r);
        if (a <= l && r <= b) {  // 完全に内側の時
            lazy[k] = x;
            eval(k, l, r);
        } else if (a < r && l < b) {                     // 一部区間が被る時
            update(a, b, x, k * 2 + 1, l, (l + r) / 2);  // 左の子
            update(a, b, x, k * 2 + 2, (l + r) / 2, r);  // 右の子
            dat[k] = dat[k * 2 + 1] + dat[k * 2 + 2];
        }
    }
    void update(int a, int b, T x) { update(a, b, x, 0, 0, n); }
};

int N;
string S;
int Q;

int main() {
  cin >> N >> S >> Q;
  vector<RSQ<int>> v;
  rep(i, 26) {
    RSQ<int> tree(N);
    v.push_back(tree);
    rep(j, N) {
      v[i].update(j, 0);
    }
  }
  rep(i, N) {
    int temp = S[i] - 'a';
    v[temp].update(i, 1);
  }
  vector<int> ans;
  rep(i, Q) {
    int temp;
    cin >> temp;
    if(temp == 1) {
      int j;
      char c;
      cin >> j >> c;
      j--;
      v[S[j]-'a'].update(j, 0);
      S[j] = c;
      v[S[j]-'a'].update(j, 1);
    } else {
      int l, r;
      cin >> l >> r;
      l--;r--;
      int t = 0;
      rep(j, 26) {
        if(v[j].query(l, r + 1)) {
          t++;
        }
      }
      ans.push_back(t);
    }
  }
  rep(i, ans.size()) {
      cout << ans[i] << endl;
  }
  return 0;
}

Measured memory_bytes: 425696