using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using System.Globalization;
using System.Collections;

namespace Atcoder
{

    class Program
    {
       public static List<int>[] Togo;
        public static UnionFind find;
        static void Main(string[] args)
        {

            StreamScanner scan = new StreamScanner(System.Console.OpenStandardInput());
            int n = scan.Integer();
            char[] s = scan.Scan().ToCharArray();
            int q = scan.Integer();
            //int[,] ic = scan.Integer(q, 3);
        
            Set<int>[] sets = new Set<int>[26];
            for (int i = 0; i < 26; i++)
            {
                sets[i] = new Set<int>();
            }
            for (int i = 0; i < n; i++)
            {
                sets[s[i] - 'a'].Insert(i);
            }
            for (int i = 0; i < q; i++)
            {
                int ban = scan.Integer();
                int one = scan.Integer()-1;
                if (ban == 1)
                {
                    char two = scan.Char();
                    sets[s[one]-'a'].Remove(one);
                    s[one] = two;
                    sets[two- 'a'].Insert(one);
                }
                else
                {
                    int sum = 0;
                    int two = scan.Integer() - 1;
                    for (int j = 0; j < 26; j++)
                    {
                        if (sets[j] == null) { continue; }
                        int lower = sets[j].LowerBound(one);
                        if (lower == sets[j].Count() || lower < 0) { continue; }
                        if (sets[j].ElementAt(lower) <= two) { sum++; }
                    }
                    Console.WriteLine(sum);
                }
            }
      /*      for (int i = 0; i < q; i++)
            {
                if (ic[i, 0] == 1)
                {
                    sets[s[ic[i, 1] - 'a']].Remove(ic[i, 1]);
                    s[ic[i, 1]] = (char)ic[i, 2];
                    sets[s[ic[i, 1] - 'a']].Insert(ic[i, 1]);
                }
                else
                {
                    int sum = 0;
                    for (int j = 0; j < 26; j++)
                    {
                        int lower = sets[j].LowerBound(ic[i, 1]);
                        if (lower <= ic[i, 2]) { sum++; }
                    }
                    Console.WriteLine(sum);
                }
            }*/
            

        }
        static int solve(int[][] s, int k, int defkaisu)
        {
            int w = s[0].Length;
            int h = s.GetLength(0);
            int[] buf = new int[h];
            int kaisu = defkaisu;
            bool done = false;
            for (int j = 0; j < w; j++)
            {
                done = false;
                for (int i = 0; i < h; i++)
                {
                    if (done) { continue; }
                    if (s[i][j] > k) { return int.MaxValue; }
                    if (j > 0)
                    {
                        if (buf[i] + s[i][j] > k)
                        {
                            for (int l = 0; l < h; l++)
                            {
                                buf[l] = s[l][j];
                            }
                            kaisu++;done = true;
                        }
                        else
                        {
                            buf[i] += s[i][j];
                        }
                    }
                    else
                    {
                        for (int l = 0; l < h; l++)
                        {
                            buf[l] = s[l][j]; 
                        }
                    }//j=0で↑をやり過ごしているならカットは不要
                }
            }
            return kaisu;
        }
        
        static List<int> solve(int n,int ima)//nokoriだけ
        {
            if (ima == 0)
            {
                for (int i = 0; i < Togo[n].Count; i++)
                {
                    var buf = solve(Togo[n][i], 1);
                    for (int j = 0; j < buf.Count; j++)
                    {
                        find.Unite(n, buf[j]);
                    }
                }
                return null;
            }
            else if(ima==3)
            {
                var buf = new List<int>();
                buf.Add(n);
                return buf;
            }
            else
            {
                var ret = new List<int>();
                for (int i = 0; i < Togo[n].Count; i++)
                {
                    var buf = solve(Togo[n][i], ima++);
                    ret.AddRange(buf);
                }
                return ret;
            }
        }
        static long Syakutori(int[]a,long k)//しゃくとり法モデル:連続部分列に含まれるすべての要素の値の和がK以上
        {
            long sum = 0;
            int right = 0;
            long ans = 0;
            for (int left = 0; left < a.Length; left++)
            {
                while (right < a.Length&&sum<k)
                {
                    sum += a[right];right++;
                
                }
                if (k <= sum) { ans += a.Length - right + 1; }
                if (left == right) { right++; }
                else { sum -= a[left]; }
            }
            return ans;
        }
       static int[] zalgo(String s)
        {
            int n = s.Length;
            int[] ret = new int[n];
            ret[0] = n;
            int i = 1, j = 0;
            while (i < n)
            {
                while (i + j < n && s[j] == s[i + j])
                    ++j;
                ret[i] = j;
                if (j == 0)
                {
                    ++i;
                    continue;
                }
                int k = 1;
                while (i + k < n && k + ret[k] < j)
                {
                    ret[i + k] = ret[k];
                    ++k;
                }
                i += k;
                j -= k;
            }
            return ret;
        }
        static int gcd(int a,int b)
        {
            if (a % b == 0) { return b; }
            return gcd(b, a % b);
        }
        static int[]yousogotoplus(int[]a,int[]b)
        {
            int[] c = new int[a.Length];
            for (int i = 0; i < a.Length; i++)
            {
                c[i] = a[i] + b[i];
            }
            return c;
        }
        static int[] yousogotoplus(int[] a, int[,] b, int l)
        {
            int[] c = new int[a.Length];
            for (int i = 0; i < a.Length; i++)
            {
                c[i] = a[i] + b[l,i];
            }
            return c;
        }
        
        static int[] yousogotoplus(int[,] a,int l)
        {
            int[] c = new int[a.GetLength(1)];
            for (int i = 0; i < a.GetLength(1); i++)
            {
                c[i] = a[l, i];
            }
            return c;
        }
        static long solve(int mae,int ushiro,long[,]dp,int[]a,bool taro)
        {
            if (dp[mae, ushiro] > long.MinValue)
            {
                return dp[mae, ushiro];
            }
            if (ushiro - mae == 1)
            {
                if (taro) { dp[mae, ushiro] = a[mae]; return dp[mae, ushiro]; }
                else { dp[mae, ushiro] = -a[mae]; return dp[mae, ushiro]; }
            }

            if(taro)
            {
                long max = Math.Max(a[mae] + solve(mae + 1, ushiro, dp, a, !taro),
                    a[ushiro-1] + solve(mae, ushiro - 1, dp, a, !taro));
                dp[mae, ushiro] = max;
                return dp[mae, ushiro];
                
            }
            else
            {
                long min= Math.Min(-a[mae] + solve(mae + 1, ushiro, dp, a, !taro),
                    -a[ushiro-1] + solve(mae, ushiro - 1, dp, a, !taro));
                dp[mae, ushiro] = min;
                return dp[mae, ushiro];
            }
            
        }

        static int nibutan(int med,int[]a)//med以上が何個あるか
        {
            int mae = 0;
            int ato = a.Length;
            while (true)
            {
                if (ato - mae <= 1) {
                    return mae; }
                int mid = (mae + ato) / 2;
                if (a[mid] > med) { ato = mid; }
                if (a[mid] <= med) { mae = mid; }
            }
        }
        static int binary_search(int key, int[] moto)
        {
            int ng = -1;
            int ok = moto.Length;
            while (Math.Abs(ok - ng) > 1)
            {
                int mid = (ok + ng) / 2;
                if (isOK(mid, key, moto)) { ok = mid; }
                else { ng = mid; }
            }
            return ok;
        }
        static bool isOK(int index, int key,int[]moto)
        {
            return moto[index] >= key;
        }

        public static int h;
        public static int w;
        public static int BFS(int height, int width, char[,] map)
        {
            int[] dx = new int[] { -1, 0, 0, 1 };
            int[] dy = new int[] { 0, 1, -1, 0 };
            bool[,] visited = new bool[h, w];
            int[,] len = new int[h, w];
            var queue = new Queue<Tuple<int, int>>();
            queue.Enqueue(new Tuple<int, int>(height, width));
            int max = 0;
            visited[height, width] = true;

            while (queue.Count > 0)
            {
                var now = queue.Dequeue();
                for (int i = 0; i < 4; i++)
                {
                    int ny = now.Item1 + dy[i];
                    int nx = now.Item2 + dx[i];
                    if (ny >= h || ny < 0 || nx >= w || nx < 0)
                        continue;
                    if (visited[ny, nx] || map[ny, nx] == '#')
                        continue;
                    visited[ny, nx] = true;
                    len[ny, nx] = len[now.Item1, now.Item2] + 1;
                    max = Math.Max(max, len[ny, nx]);
                    queue.Enqueue(new Tuple<int, int>(ny, nx));
                }
            }
            return max;
        }
    }
    public class StreamScanner
    {
        public StreamScanner(Stream stream) { str = stream; }
        private readonly Stream str;
        private readonly byte[] buf = new byte[1024];
        private int len, ptr;
        public bool isEof = false;
        public bool IsEndOfStream { get { return isEof; } }
        private byte read()
        {
            if (isEof) throw new EndOfStreamException();
            if (ptr >= len)
            {
                ptr = 0;
                if ((len = str.Read(buf, 0, 1024)) <= 0)
                {
                    isEof = true;
                    return 0;
                }
            }
            return buf[ptr++];
        }
        public char Char()
        {
            byte b = 0;
            do b = read();
            while (b < 33 || 126 < b);
            return (char)b;
        }
        public char[] Char(int n)
        {
            char[] kore = new char[n];
            for (int i = 0; i < n; i++)
            {
                kore[i] = Char();
            }
            return kore;
        }
        public string Scan()
        {
            var sb = new StringBuilder();
            for (var b = Char(); b >= 33 && b <= 126; b = (char)read())
                sb.Append(b);
            return sb.ToString();
        }
        public long Long()
        {
            long ret = 0; byte b = 0; var ng = false;
            do b = read();
            while (b != '-' && (b < '0' || '9' < b));
            if (b == '-') { ng = true; b = read(); }
            for (; true; b = read())
            {
                if (b < '0' || '9' < b)
                    return ng ? -ret : ret;
                else ret = ret * 10 + b - '0';
            }
        }
        public long[]Long(int n)
        {
            long[] kore = new long[n];
            for (int i = 0; i < n; i++)
            {
                kore[i] = Long();
            }
            return kore;
        }
        public int Integer() { return (int)Long(); }
        public int[]Integer(int n)
        {
            int[] kore = new int[n];
            for (int i = 0; i < n; i++)
            {
                kore[i] = (int)Long();
            }
            return kore;
        }
        public int[,] Integer(int h,int w)
        {
            int[,] kore = new int[h,w];
            for (int i = 0; i < h; i++)
            {
                for (int j = 0; j < w; j++)
                {
                    kore[i, j] = (int)Long();
                }
                
            }
            return kore;
        }

        public int[] StringToInt(int n)
        {
            char[] s = Scan().ToCharArray();
            int[] kore = new int[n];
            for (int i = 0; i < n; i++)
            {
                kore[i] = s[i] - '0';
            }
            return kore;
        }
        public int[,] StringToInt(int h,int w)
        {
         
            int[,] kore = new int[h,w];
            for (int i = 0; i < h; i++)
            {
                char[] s = Scan().ToCharArray();
                for (int j = 0; j < w; j++)
                {
                    kore[i,j] = s[j] - '0';
                }
               
            }
            return kore;
        }

        public double Double() { return double.Parse(Scan(), CultureInfo.InvariantCulture); }
    }
    public class UnionFind
    {
        public int[] data;

        public UnionFind(int size)
        {
            data = new int[size];
            for (int i = 0; i < size; i++) data[i] = -1;
        }

        public bool Unite(int x, int y)
        {
            x = Root(x);
            y = Root(y);

            if (x != y)
            {
                if (data[y] < data[x])
                {
                    var tmp = y;
                    y = x;
                    x = tmp;
                }
                data[x] += data[y];
                data[y] = x;
            }
            return x != y;
        }

        public bool IsSameGroup(int x, int y)
        {
            return Root(x) == Root(y);
        }

        public int Root(int x)
        {
            return data[x] < 0 ? x : data[x] = Root(data[x]);
        }
    }
    public class Mlong
    {
        private static long mod = 1000000007;
        public long num;
        public Mlong(long num) { this.num = num; }
        public override int GetHashCode()
        {
            return num.GetHashCode();
        }
        public override bool Equals(object obj)
        {
            return num.Equals(obj);
        }
        public static Mlong operator +(Mlong x, Mlong y)
        {
            return new Mlong((x.num + y.num) % mod);
        }
        public static Mlong operator -(Mlong x, Mlong y)
        {
            return new Mlong((x.num - y.num + mod) % mod);
        }
        public static Mlong operator *(Mlong x, Mlong y)
        {
            return new Mlong((long)(((1L * x.num) * y.num) % mod));
        }
        public Mlong hutujyo(Mlong x)
        {
            return new Mlong(num / x.num);
        }
        public Mlong modjyo(Mlong x)
        {
            return num * x.Pow(mod - 2);
        }
        public static implicit operator Mlong(long x)
        {
            return new Mlong(x);
        }
        public static Mlong operator %(Mlong x, Mlong y)
        {
            return new Mlong(x.num % y.num);
        }
        public static bool operator ==(Mlong x, Mlong y)
        {
            return x.num == y.num;
        }
        public static bool operator !=(Mlong x, Mlong y)
        {
            return x.num != y.num;
        }
        public static bool operator <(Mlong x, Mlong y)
        {
            return x.num < y.num;
        }
        public static bool operator >(Mlong x, Mlong y)
        {
            return x.num > y.num;
        }
        public static bool operator <=(Mlong x, Mlong y)
        {
            return x.num <= y.num;
        }
        public static bool operator >=(Mlong x, Mlong y)
        {
            return x.num >= y.num;
        }
        public Mlong Pow(Mlong n)
        {

            if (n == 1) { return this % mod; }
            if (n % 2 == 1) { return (this * Pow(n - 1)) % mod; }
            Mlong buf = Pow(n.hutujyo(2));
            return (buf * buf) % mod;
        }
        public static Mlong Pow(Mlong a, Mlong n)
        {
            return a.Pow(n);
        }
        public static Mlong Comb(Mlong n, Mlong a)
        {
            Mlong buf1 = 1;
            Mlong buf2 = 1;
            for (long i = 0; i < a; i++)
            {
                buf1 *= n - i;
                buf2 *= (i + 1);
            }
            return buf1.modjyo(buf2);

        }
        public static void WriteLine(Mlong x)
        {
            Console.WriteLine(x.num);

        }
    }
    public class Mint
    {
        private static int mod = 1000000007;
        public int num;
        public Mint(int Num) { num = Num; }
        public override int GetHashCode()
        {
            return num.GetHashCode();
        }
        public override bool Equals(object obj)
        {
            return num.Equals(obj);
        }
        public static Mint operator +(Mint x, Mint y)
        {
            return new Mint((x.num + y.num) % mod);
        }
        public static Mint operator -(Mint x, Mint y)
        {
            return new Mint((x.num - y.num + mod) % mod);
        }
        public static Mint operator *(Mint x, Mint y)
        {
            return new Mint((int)(((1L * x.num) * y.num) % mod));
        }
        public Mint hutujyo(Mint x)
        {
            return new Mint(num / x.num);
        }
        public Mint modjyo(Mint x)
        {
            return num * x.Pow(mod - 2);
        }
        public static implicit operator Mint(int x)
        {
            return new Mint(x);
        }
        public static Mint[] Toarray(int[] x)
        {
            Mint[] buf = new Mint[x.Length];
            for (int i = 0; i < x.Length; i++)
            {
                buf[i] = x[i];
            }
            return buf;
        }
        public static explicit operator int(Mint x)
        {
            return x.num;
        }
        public static Mint operator %(Mint x, Mint y)
        {
            return new Mint(x.num % y.num);
        }
        public static bool operator ==(Mint x, Mint y)
        {
            return x.num == y.num;
        }
        public static bool operator !=(Mint x, Mint y)
        {
            return x.num != y.num;
        }
        public static bool operator <(Mint x, Mint y)
        {
            return x.num < y.num;
        }
        public static bool operator >(Mint x, Mint y)
        {
            return x.num > y.num;
        }
        public static bool operator <=(Mint x, Mint y)
        {
            return x.num <= y.num;
        }
        public static bool operator >=(Mint x, Mint y)
        {
            return x.num >= y.num;
        }
        public Mint Pow(Mint n)
        {

            if (n == 1) { return this % mod; }
            if (n % 2 == 1) { return (this * Pow(n - 1)) % mod; }
            Mint buf = Pow(n.hutujyo(2));
            return (buf * buf) % mod;
        }
        public static Mint Pow(Mint a, Mint n)
        {
            return a.Pow(n);
        }
        public static Mint Comb(Mint n, Mint a)
        {
            Mint buf1 = 1;
            Mint buf2 = 1;
            for (int i = 0; i < a; i++)
            {
                buf1 *= n - i;
                buf2 *= (i + 1);
            }
            return buf1.modjyo(buf2);

        }
        public static void WriteLine(Mint x)
        {
            Console.WriteLine(x.num);

        }
    }
    public class PriorityQueue<T> : IEnumerable<T>
    {
        private readonly List<T> _data = new List<T>();
        private readonly IComparer<T> _comparer;
        private readonly bool _isDescending;

        public PriorityQueue(IComparer<T> comparer, bool isDescending = true)
        {
            _comparer = comparer;
            _isDescending = isDescending;
        }

        public PriorityQueue(Comparison<T> comparison, bool isDescending = true)
            : this(Comparer<T>.Create(comparison), isDescending)
        {
        }

        public PriorityQueue(bool isDescending = true)
            : this(Comparer<T>.Default, isDescending)
        {
        }

        public void Enqueue(T item)
        {
            _data.Add(item);
            var childIndex = _data.Count - 1;
            while (childIndex > 0)
            {
                var parentIndex = (childIndex - 1) / 2;
                if (Compare(_data[childIndex], _data[parentIndex]) >= 0)
                    break;
                Swap(childIndex, parentIndex);
                childIndex = parentIndex;
            }
        }

        public T Dequeue()
        {
            var lastIndex = _data.Count - 1;
            var firstItem = _data[0];
            _data[0] = _data[lastIndex];
            _data.RemoveAt(lastIndex--);
            var parentIndex = 0;
            while (true)
            {
                var childIndex = parentIndex * 2 + 1;
                if (childIndex > lastIndex)
                    break;
                var rightChild = childIndex + 1;
                if (rightChild <= lastIndex && Compare(_data[rightChild], _data[childIndex]) < 0)
                    childIndex = rightChild;
                if (Compare(_data[parentIndex], _data[childIndex]) <= 0)
                    break;
                Swap(parentIndex, childIndex);
                parentIndex = childIndex;
            }
            return firstItem;
        }

        public T Peek()
        {
            return _data[0];
        }

        private void Swap(int a, int b)
        {
            var tmp = _data[a];
            _data[a] = _data[b];
            _data[b] = tmp;
        }

        private int Compare(T a, T b)
        {
            return _isDescending ? _comparer.Compare(b, a) : _comparer.Compare(a, b);
        }

        public int Count => _data.Count;

        public IEnumerator<T> GetEnumerator()
        {
            return _data.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    }

    /// <summary>
    /// Self-Balancing Binary Search Tree
    /// (using Randamized BST)
    /// </summary>
    public class SB_BinarySearchTree<T> where T : IComparable
    {
        public class Node
        {
            public T Value;
            public Node LChild;
            public Node RChild;
            public int Count;     //size of the sub tree

            public Node(T v)
            {
                Value = v;
                Count = 1;
            }
        }

        static Random _rnd = new Random();

        public static int Count(Node t)
        {
            return t == null ? 0 : t.Count;
        }

        static Node Update(Node t)
        {
            t.Count = Count(t.LChild) + Count(t.RChild) + 1;
            return t;
        }

        public static Node Merge(Node l, Node r)
        {
            if (l == null || r == null) return l == null ? r : l;

            if ((double)Count(l) / (double)(Count(l) + Count(r)) > _rnd.NextDouble())
            {
                l.RChild = Merge(l.RChild, r);
                return Update(l);
            }
            else
            {
                r.LChild = Merge(l, r.LChild);
                return Update(r);
            }
        }

        /// <summary>
        /// split as [0, k), [k, n)
        /// </summary>
        public static Tuple<Node, Node> Split(Node t, int k)
        {
            if (t == null) return new Tuple<Node, Node>(null, null);
            if (k <= Count(t.LChild))
            {
                var s = Split(t.LChild, k);
                t.LChild = s.Item2;
                return new Tuple<Node, Node>(s.Item1, Update(t));
            }
            else
            {
                var s = Split(t.RChild, k - Count(t.LChild) - 1);
                t.RChild = s.Item1;
                return new Tuple<Node, Node>(Update(t), s.Item2);
            }
        }

        public static Node Remove(Node t, T v)
        {
            if (Find(t, v) == null) return t;
            return RemoveAt(t, LowerBound(t, v));
        }

        public static Node RemoveAt(Node t, int k)
        {
            var s = Split(t, k);
            var s2 = Split(s.Item2, 1);
            return Merge(s.Item1, s2.Item2);
        }

        public static bool Contains(Node t, T v)
        {
            return Find(t, v) != null;
        }

        public static Node Find(Node t, T v)
        {
            while (t != null)
            {
                var cmp = t.Value.CompareTo(v);
                if (cmp > 0) t = t.LChild;
                else if (cmp < 0) t = t.RChild;
                else break;
            }
            return t;
        }

        public static Node FindByIndex(Node t, int idx)
        {
            if (t == null) return null;

            var currentIdx = Count(t) - Count(t.RChild) - 1;
            while (t != null)
            {
                if (currentIdx == idx) return t;
                if (currentIdx > idx)
                {
                    t = t.LChild;
                    currentIdx -= (Count(t == null ? null : t.RChild) + 1);
                }
                else
                {
                    t = t.RChild;
                    currentIdx += (Count(t == null ? null : t.LChild) + 1);
                }
            }

            return null;
        }

        public static int UpperBound(Node t, T v)
        {
            var torg = t;
            if (t == null) return -1;

            var ret = Int32.MaxValue;
            var idx = Count(t) - Count(t.RChild) - 1;
            while (t != null)
            {
                var cmp = t.Value.CompareTo(v);

                if (cmp > 0)
                {
                    ret = Math.Min(ret, idx);
                    t = t.LChild;
                    idx -= (Count(t == null ? null : t.RChild) + 1);
                }
                else if (cmp <= 0)
                {
                    t = t.RChild;
                    idx += (Count(t == null ? null : t.LChild) + 1);
                }
            }
            return ret == Int32.MaxValue ? Count(torg) : ret;
        }

        public static int LowerBound(Node t, T v)
        {
            var torg = t;
            if (t == null) return -1;

            var idx = Count(t) - Count(t.RChild) - 1;
            var ret = Int32.MaxValue;
            while (t != null)
            {
                var cmp = t.Value.CompareTo(v);
                if (cmp >= 0)
                {
                    if (cmp == 0) ret = Math.Min(ret, idx);
                    t = t.LChild;
                    if (t == null) ret = Math.Min(ret, idx);
                    idx -= t == null ? 0 : (Count(t.RChild) + 1);
                }
                else if (cmp < 0)
                {
                    t = t.RChild;
                    idx += (Count(t == null ? null : t.LChild) + 1);
                    if (t == null) return idx;
                }
            }
           // return ret;
           return ret == Int32.MaxValue ? Count(torg) : ret;
        }

        public static Node Insert(Node t, T v)
        {
            var ub = LowerBound(t, v);
            return InsertByIdx(t, ub, v);
        }

        static Node InsertByIdx(Node t, int k, T v)
        {
            var s = Split(t, k);
            return Merge(Merge(s.Item1, new Node(v)), s.Item2);
        }

        public static IEnumerable<T> Enumerate(Node t)
        {
            var ret = new List<T>();
            Enumerate(t, ret);
            return ret;
        }

        static void Enumerate(Node t, List<T> ret)
        {
            if (t == null) return;
            Enumerate(t.LChild, ret);
            ret.Add(t.Value);
            Enumerate(t.RChild, ret);
        }
    }

    /// <summary>
    /// C-like set
    /// </summary>
    public class Set<T> where T : IComparable
    {
        protected SB_BinarySearchTree<T>.Node _root;

        public T this[int idx] { get { return ElementAt(idx); } }

        public int Count()
        {
            return SB_BinarySearchTree<T>.Count(_root);
        }

        public virtual void Insert(T v)
        {
            if (_root == null) _root = new SB_BinarySearchTree<T>.Node(v);
            else
            {
                if (SB_BinarySearchTree<T>.Find(_root, v) != null) return;
                _root = SB_BinarySearchTree<T>.Insert(_root, v);
            }
        }

        public void Clear()
        {
            _root = null;
        }

        public void Remove(T v)
        {
            _root = SB_BinarySearchTree<T>.Remove(_root, v);
        }

        public bool Contains(T v)
        {
            return SB_BinarySearchTree<T>.Contains(_root, v);
        }

        public T ElementAt(int k)
        {
            var node = SB_BinarySearchTree<T>.FindByIndex(_root, k);
            if (node == null) throw new IndexOutOfRangeException();
            return node.Value;
        }

        public int Count(T v)
        {
            return SB_BinarySearchTree<T>.UpperBound(_root, v) - SB_BinarySearchTree<T>.LowerBound(_root, v);
        }

        public int LowerBound(T v)
        {
            return SB_BinarySearchTree<T>.LowerBound(_root, v);
        }

        public int UpperBound(T v)
        {
            return SB_BinarySearchTree<T>.UpperBound(_root, v);
        }

        public Tuple<int, int> EqualRange(T v)
        {
            if (!Contains(v)) return new Tuple<int, int>(-1, -1);
            return new Tuple<int, int>(SB_BinarySearchTree<T>.LowerBound(_root, v), SB_BinarySearchTree<T>.UpperBound(_root, v) - 1);
        }

        public List<T> ToList()
        {
            return new List<T>(SB_BinarySearchTree<T>.Enumerate(_root));
        }
    }

    /// <summary>
    /// C-like multiset
    /// </summary>
    public class MultiSet<T> : Set<T> where T : IComparable
    {
        public override void Insert(T v)
        {
            if (_root == null) _root = new SB_BinarySearchTree<T>.Node(v);
            else _root = SB_BinarySearchTree<T>.Insert(_root, v);
        }
    }
}


Measured memory_bytes: 40452