#include <bits/stdc++.h>
using namespace std;
#define def auto
inline int chic(char c)
{
return ((c >= 'A' && c <= 'Z') ? c - 'A' : c - 'a');
}
struct ODT
{
int l, r;
mutable int v;
ODT(const int &il, const int &ir, const int &iv):
l(il), r(ir), v(iv)
{
}
inline bool operator<(const ODT&o) const
{
return l < o.l;
}
};
inline bool cmp(const ODT a, const ODT b)
{
return a.v < b.v;
}
set<ODT> odt;
int n, m, bucket[27];
inline def split(int x)
{
if (x > n)
{
return odt.end();
}
auto it = --odt.upper_bound(ODT(x, 0, 0));
if(it->l == x)
{
return it;
}
int l = it->l, r = it->r, v = it->v;
odt.erase(it);
odt.insert(ODT(l, x - 1, v));
return odt.insert(ODT(x, r, v)).first;
}
inline int count(int l, int r, int k)
{
auto itr = split(r + 1), itl = split(l);
int ans = 0;
for(; itl != itr; ++itl)
{
if(itl->v == k)
{
ans += itl->r - itl->l + 1;
}
}
return ans;
}
inline void assign(int l, int r, int v)
{
auto itr = split(r + 1), itl = split(l);
odt.erase(itl, itr);
odt.insert(ODT(l, r, v));
}
inline void sort(int l, int r)
{
auto itr = split(r + 1), itl = split(l);
int cur = l, i;
memset(bucket, 0, sizeof bucket);
for(; itl != itr; itl++)
{
bucket[itl->v] += itl->r - itl-> l + 1;
odt.erase(itl);
}
for(i = 0; i <= 25; i++)
{
if(bucket[i] != 0)
{
odt.insert(ODT(cur, cur + bucket[i], i));
cur += bucket[i] + 1;
}
}
}
int main()
{
int i, ltc, l = 1, opt, x, y;
char tc, k;
cin>>n>>m;
cin>>tc;
ltc = chic(tc);
for(i = 2; i <= n; i++)
{
cin>>tc;
if(ltc != chic(tc))
{
odt.insert(ODT(l, i - 1, ltc));
ltc = chic(tc);
l = i;
}
}
odt.insert(ODT(l, n, chic(tc)));
while(m--)
{
cin>>opt>>x>>y;
switch(opt)
{
case 1:
{
cin>>k;
cout<<count(x, y, chic(k))<<endl;
break;
}
case 2:
{
cin>>k;
assign(x, y, chic(k));
break;
}
case 3:
{
sort(x, y);
break;
}
}
}
return 0;
}