Buggy python submission (verdict: Time Limit Exceeded):
#!/usr/bin/env python3
import sys
import math

def solve(N, S, Q, queries):
    def update(i, c):
        values[i] = c
        s = (i // sN) * sN
        buckets[i//sN] = 0
        for v in values[s:min(s+sN, len(values))]:
            buckets[i//sN] |= 1 << v

    def count(l, r):
        res = 0
        for i in range(sN):
            ll = i * sN
            rr = ll + sN
            if r <= ll or rr <= l:
                continue
            if l <= ll and rr <= r:
                res |= buckets[i]
            else:
                for j in range(max(l, ll), min(r, rr)):
                    res |= 1 << values[j]
        return sum(b == '1' for b in bin(res))
        
    sN = math.ceil(N ** 0.5)
    values = [ord(c)-ord('a') for c in S]
    buckets = [0] * sN
    for i, v in enumerate(values):
        buckets[i//sN] |= 1 << v
    for t, x, y in queries:
        if t == 1:
            update(int(x)-1, ord(y)-ord('a'))
        else:
            yield count(int(x)-1, int(y))

# Generated by 1.1.6 https://github.com/kyuridenamida/atcoder-tools  (tips: You use the default template now. You can remove this line by using your custom template)
def main():
    def iterate_tokens():
        for line in sys.stdin:
            for word in line.split():
                yield word
    tokens = iterate_tokens()
    N = int(next(tokens))
    S = next(tokens)
    Q = int(next(tokens))
    queries = [(int(next(tokens)), next(tokens), next(tokens)) for _ in range(Q)]
    for result in solve(N, S, Q, queries):
        print(result)

if __name__ == '__main__':
    main()


Fixed submission:
#!/usr/bin/env python3
import sys
import math

def solve(N, S, Q, queries):
    def update(i, c):
        values[i] = c
        s = (i // sN) * sN
        buckets[i//sN] = 0
        for v in values[s:min(s+sN, len(values))]:
            buckets[i//sN] |= 1 << v

    def count(l, r):
        res = 0
        for i in range(sN):
            ll = i * sN
            rr = ll + sN
            if r <= ll or rr <= l:
                continue
            if l <= ll and rr <= r:
                res |= buckets[i]
            else:
                for j in range(max(l, ll), min(r, rr)):
                    res |= 1 << values[j]
        return sum(b == '1' for b in bin(res))
        
    sN = math.ceil(N ** 0.5)
    values = [ord(c)-ord('a') for c in S]
    buckets = [0] * sN
    for i, v in enumerate(values):
        buckets[i//sN] |= 1 << v
    for t, x, y in queries:
        if t == 1:
            update(int(x)-1, ord(y)-ord('a'))
        else:
            yield count(int(x)-1, int(y))

# Generated by 1.1.6 https://github.com/kyuridenamida/atcoder-tools  (tips: You use the default template now. You can remove this line by using your custom template)
def main():
    def iterate_tokens():
        for line in sys.stdin:
            for word in line.split():
                yield word
    tokens = iterate_tokens()
    N = int(next(tokens))
    S = next(tokens)
    Q = int(next(tokens))
    queries = [(int(next(tokens)), next(tokens), next(tokens)) for _ in range(Q)]
    for result in solve(N, S, Q, queries):
        print(result)

if __name__ == '__main__':
    main()

Buggy python submission (verdict: Time Limit Exceeded):
#!/usr/bin/env python3
import sys
import math
 
def solve(N, S, Q, queries):
    def update(i, c):
        cc = values[i]
        buckets[i//sN][cc] -= 1
        buckets[i//sN][c] += 1
        values[i] = c
 
    def count(l, r):
        res = [0] * (ord('z')-ord('a')+1)
        for i in range(sN):
            ll = i * sN
            rr = ll + sN
            if r <= ll or rr <= l:
                continue
            if l <= ll and rr <= r:
                for i, v in enumerate(buckets[i]):
                    res[i] += v
            else:
                for i in range(max(l, ll), min(r, rr)):
                    res[values[i]] += 1
        return sum(n > 0 for n in res)
        
    sN = math.ceil(N ** 0.5)
    values = [ord(c)-ord('a') for c in S]
    buckets = [[0] * (ord('z')-ord('a')+1) for _ in range(sN)]
    for i, v in enumerate(values):
        buckets[i//sN][v] += 1
    for t, x, y in queries:
        if t == 1:
            update(int(x)-1, ord(y)-ord('a'))
        else:
            yield count(int(x)-1, int(y))
 
# Generated by 1.1.6 https://github.com/kyuridenamida/atcoder-tools  (tips: You use the default template now. You can remove this line by using your custom template)
def main():
    def iterate_tokens():
        for line in sys.stdin:
            for word in line.split():
                yield word
    tokens = iterate_tokens()
    N = int(next(tokens))
    S = next(tokens)
    Q = int(next(tokens))
    queries = [(int(next(tokens)), next(tokens), next(tokens)) for _ in range(Q)]
    for result in solve(N, S, Q, queries):
        print(result)
 
if __name__ == '__main__':
    main()

Fixed submission:
#!/usr/bin/env python3
import sys
import math

def solve(N, S, Q, queries):
    def update(i, c):
        values[i] = c
        s = (i // sN) * sN
        buckets[i//sN] = 0
        for v in values[s:min(s+sN, len(values))]:
            buckets[i//sN] |= 1 << v

    def count(l, r):
        res = 0
        for i in range(sN):
            ll = i * sN
            rr = ll + sN
            if r <= ll or rr <= l:
                continue
            if l <= ll and rr <= r:
                res |= buckets[i]
            else:
                for j in range(max(l, ll), min(r, rr)):
                    res |= 1 << values[j]
        return sum(b == '1' for b in bin(res))
        
    sN = math.ceil(N ** 0.5)
    values = [ord(c)-ord('a') for c in S]
    buckets = [0] * sN
    for i, v in enumerate(values):
        buckets[i//sN] |= 1 << v
    for t, x, y in queries:
        if t == 1:
            update(int(x)-1, ord(y)-ord('a'))
        else:
            yield count(int(x)-1, int(y))

# Generated by 1.1.6 https://github.com/kyuridenamida/atcoder-tools  (tips: You use the default template now. You can remove this line by using your custom template)
def main():
    def iterate_tokens():
        for line in sys.stdin:
            for word in line.split():
                yield word
    tokens = iterate_tokens()
    N = int(next(tokens))
    S = next(tokens)
    Q = int(next(tokens))
    queries = [(int(next(tokens)), next(tokens), next(tokens)) for _ in range(Q)]
    for result in solve(N, S, Q, queries):
        print(result)

if __name__ == '__main__':
    main()

Buggy python submission (verdict: Time Limit Exceeded):
#!/usr/bin/env python3
import sys
import math

def solve(N, S, Q, queries):
    def update(i, c):
        cc = values[i]
        buckets[i//sN][cc] -= 1
        buckets[i//sN][c] += 1
        values[i] = c

    def count(l, r):
        res = [0] * (ord('z')-ord('a')+1)
        for i in range(sN):
            ll = i * sN
            rr = ll + sN
            if r <= ll or rr <= l:
                continue
            if l <= ll and rr <= r:
                for i, v in enumerate(buckets[i]):
                    res[i] += v
            else:
                for i in range(max(l, ll), min(r, rr)):
                    res[values[i]] += 1
        return sum(n > 0 for n in res)
        
    sN = math.ceil(N ** 0.5)
    values = [ord(c)-ord('a') for c in S]
    buckets = [[0] * (ord('z')-ord('a')+1) for _ in range(sN)]
    for i, v in enumerate(values):
        buckets[i//sN][v] += 1
    for t, x, y in queries:
        if t == 1:
            update(int(x)-1, ord(y)-ord('a'))
        else:
            yield count(int(x)-1, int(y))

# Generated by 1.1.6 https://github.com/kyuridenamida/atcoder-tools  (tips: You use the default template now. You can remove this line by using your custom template)
def main():
    def iterate_tokens():
        for line in sys.stdin:
            for word in line.split():
                yield word
    tokens = iterate_tokens()
    N = int(next(tokens))
    S = next(tokens)
    Q = int(next(tokens))
    queries = [(int(next(tokens)), next(tokens), next(tokens)) for _ in range(Q)]
    for result in solve(N, S, Q, queries):
        print(result)

if __name__ == '__main__':
    main()


Fixed submission:
#!/usr/bin/env python3
import sys
import math

def solve(N, S, Q, queries):
    def update(i, c):
        values[i] = c
        s = (i // sN) * sN
        buckets[i//sN] = 0
        for v in values[s:min(s+sN, len(values))]:
            buckets[i//sN] |= 1 << v

    def count(l, r):
        res = 0
        for i in range(sN):
            ll = i * sN
            rr = ll + sN
            if r <= ll or rr <= l:
                continue
            if l <= ll and rr <= r:
                res |= buckets[i]
            else:
                for j in range(max(l, ll), min(r, rr)):
                    res |= 1 << values[j]
        return sum(b == '1' for b in bin(res))
        
    sN = math.ceil(N ** 0.5)
    values = [ord(c)-ord('a') for c in S]
    buckets = [0] * sN
    for i, v in enumerate(values):
        buckets[i//sN] |= 1 << v
    for t, x, y in queries:
        if t == 1:
            update(int(x)-1, ord(y)-ord('a'))
        else:
            yield count(int(x)-1, int(y))

# Generated by 1.1.6 https://github.com/kyuridenamida/atcoder-tools  (tips: You use the default template now. You can remove this line by using your custom template)
def main():
    def iterate_tokens():
        for line in sys.stdin:
            for word in line.split():
                yield word
    tokens = iterate_tokens()
    N = int(next(tokens))
    S = next(tokens)
    Q = int(next(tokens))
    queries = [(int(next(tokens)), next(tokens), next(tokens)) for _ in range(Q)]
    for result in solve(N, S, Q, queries):
        print(result)

if __name__ == '__main__':
    main()