// #pragma GCC optimize("O3")
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pb push_back
#define N 1000000007
#define N2 998244353
#define Nmax 500005
#define INF (ll)1e18
#define pll pair<ll,ll>
#define mp make_pair
#define fi first
#define se second
#define vv vector
#define rep(i,n) rep2(i,0,n)
#define rep2(i,m,n) for(ll i=m;i<(n);i++)
#define repr(i,n) repr2(i,n,0)
#define repr2(i,m,n) for(ll i=m;i>=(n);i--)
#define get(n) ll (n); scanf("%lld",&(n));
#define get2(n,k) ll (n),(k); scanf("%lld%lld",&(n),&(k));
#define get3(n,m,k) ll (n),(m),(k); scanf("%lld%lld%lld",&(n),&(m),&(k));
#define gets(s) string (s); cin >> (s);
#define ALL(c) (c).begin(),(c).end()
#define fill_nums(A,n) vector<ll> A((n)); for(ll i=0;i<n;i++) scanf("%lld",&A[i]);
#define fill_edges(adj,m) for(ll i=0;i<m;i++) {ll a, b; scanf("%lld %lld",&a,&b); adj[a-1].pb(b-1); adj[b-1].pb(a-1);}
#define fill_direct(adj,m) for(ll i=0;i<m;i++) {ll a, b; scanf("%lld %lld",&a,&b); adj[a-1].pb(b-1);}
#define StarBurstStream ios_base::sync_with_stdio(false); cin.tie(0); //cout.tie(0);
#define pi 3.1415926535897L
#define db long double  /* print with %Lf */
#define EPS 1e-9
#define flu fflush(stdout)
#define imp {cout<<"No\n"; return 0;}
#define yup {cout<<"Yes\n"; return 0;}
#define debug_vec(vec,n) {rep(idxi,n) cout<<vec[idxi]<<" "; cout<<"\n";}
#define debug_pvec(vec,n) {rep(idxi,n) cout<<vec[idxi].fi<<"-"<<vec[idxi].se<<"  "; cout<<"\n";}
#define debug_set(seta) {cout<<seta.size()<<"\n"; for(auto idxi:seta) cout<<idxi<<" "; cout<<"\n";}
#define debug_map(mappa) {cout<<mappa.size()<<"\n";for(auto idxi:mappa) cout<<idxi.fi<<"-"<<idxi.se<<"  "; cout<<"\n";}
#define debug_vecvec(vec,n) {rep(idxi,n) {for(auto idxi2:vec[idxi]) cout<<idxi2<<" "; cout<<"\n";}}
 
// LOOK AT THESE BEFORE SUBMISSION
// LOOK AT THESE BEFORE SUBMISSION
// ============================================================
// Nmax value and is index access offset taking it beyond Nmax.
// Precision error in case of real numbers.
// N vs N2 chosen correctly.
// Modulo applied everywhere.
// Flush in case of interactive.
// Don't erase value in multiset.
// Check for array indices out of bound (in pre or suff sums).
// Correct return value for functions.
// Confirm the letter case of yes and no.
// Dont paste code two times in the text file.
// ============================================================

ll tree[4*Nmax];    
ll A[Nmax];

void make_tree(ll idx, ll low, ll high) {
    
    if(low==high) {
        tree[idx] = A[low];
        return;
    }

    ll mid = low + (high-low)/2;
    make_tree((idx<<1),low,mid);
    make_tree((idx<<1)+1,mid+1,high);
    tree[idx] = tree[(idx<<1)] | tree[(idx<<1)+1];
}

ll find(ll idx, ll low, ll high, ll l, ll r) {
    
    if(l>high || r<low)
        return 0;

    if(l<=low && high<=r)
        return tree[idx];

    ll mid = low + (high-low)/2;
    if(mid<l)
        return find((idx<<1)+1,mid+1,high,l,r);
    if(r<=mid)
        return find((idx<<1),low,mid,l,r);

    return find((idx<<1),low,mid,l,r) | find((idx<<1)+1,mid+1,high,l,r);
}

void update(ll idx, ll low, ll high, ll pos, ll val) {
    
    if(low==high) {
        A[low] = val;
        tree[idx] = val;
        return;
    }

    ll mid = low + (high-low)/2;

    if(pos<=mid)
        update((idx<<1),low,mid,pos,val);
    else
        update((idx<<1)+1,mid+1,high,pos,val);

    tree[idx] = tree[(idx<<1)] | tree[(idx<<1)+1];
}


int main() {

    memset(tree, 0, sizeof(tree));
    memset(A, 0, sizeof(A));

    get(n);
    gets(s);
    rep(i,n) {
        A[i] = s[i]-'a';
        A[i] = (ll)1<<A[i];
    }
    make_tree(1,0,n-1);

    get(q);
    rep(i,q) {
        get(a); 

        if(a==1) {
            get(b);b--; gets(c);
            int ab = (ll)1<<(c[0]-'a');
            update(1,0,n-1,b,ab);
        }
        else {
            get2(b,c);
            --b; --c;
            ll ans = __builtin_popcount(find(1,0,n-1,b,c));
            printf("%lld\n", ans);
        }
    }

    return 0;  
}   


Measured memory_bytes: 20484