🚀 Alguni Start learning

Unit 15: Counting Without Listing

Answers with more digits than the universe has atoms.

Unit 15 of 25 in Competitive programming for kids. Its 4 lessons are Choose, Pascal's Triangle, Add Some, Take Some Back and Catalan Numbers — below is everything each one explains, and a question or two from it to try.

Every sample on this page was run through real Python before it shipped, and prints exactly what it says it prints.

This unit opens with a fortnight’s trial of everything — no card needed — or with a family plan, bought in the iPhone app. The first two units of every track are free for ever. Try it in the app.

🎲 Choose

Multiply the choices

Three shirts and four hats make twelve outfits. Choices that do not interfere multiply — that is the whole of counting, and everything else is a special case.

Ordering n things: n choices, then n - 1, then n - 2. That is n factorial, which unit 2 already watched explode.

Choosing k from n, when the order does not matter

Line up k of them in order — n * (n - 1) * ... ways — then divide by the k! orders that are the same handful.

Written this way it never leaves whole numbers, because after i + 1 steps the product is always divisible by i + 1.

Python

def choose(n, k):
    result = 1
    for i in range(k):
        result = result * (n - i) // (i + 1)
    return result

print(choose(5, 2))
print(choose(10, 3))
print(choose(52, 5))

It prints

10
120
2598960

Two facts to keep in your head

Choosing k is the same as choosing which n - k to leave out, so choose(n, k) equals choose(n, n - k).

And adding up a whole row of them gives 2 ** n — because choosing any subset is what the row counts.

Python

def choose(n, k):
    result = 1
    for i in range(k):
        result = result * (n - i) // (i + 1)
    return result

print(choose(10, 3), choose(10, 7))
print(sum(choose(10, k) for k in range(11)), 2 ** 10)

It prints

120 120
1024 1024

Try it yourself

How many ways can 5 cards be dealt from 52, if the order in your hand does not matter?

  • 311875200
  • 2598960
  • 52
  • 380204032

What does this print?

Python

def choose(n, k):
    result = 1
    for i in range(k):
        result = result * (n - i) // (i + 1)
    return result

print(choose(6, 0), choose(6, 1), choose(6, 6))

Answer them in the app

🔺 Pascal's Triangle

Each number is the two above it added together

To choose k from n, either the last item is in your group — then choose k - 1 from the rest — or it is not, and you choose k from the rest.

So choose(n, k) is choose(n-1, k-1) + choose(n-1, k), which is a table you fill in row by row.

Python

rows = 6
table = [[1] * (r + 1) for r in range(rows)]
for r in range(2, rows):
    for c in range(1, r):
        table[r][c] = table[r - 1][c - 1] + table[r - 1][c]

for row in table:
    print(row)

It prints

[1]
[1, 1]
[1, 2, 1]
[1, 3, 3, 1]
[1, 4, 6, 4, 1]
[1, 5, 10, 10, 5, 1]

Why bother, when the formula is shorter?

Two reasons a contest cares about. The table needs only addition, so it works under any modulus, prime or not — no inverses required.

And it hands you every value at once, which is what a dynamic programming solution usually wants.

Python

MOD = 1000
rows = 10
table = [[1] * (r + 1) for r in range(rows)]
for r in range(2, rows):
    for c in range(1, r):
        table[r][c] = (table[r - 1][c - 1] + table[r - 1][c]) % MOD

print(table[9])

It prints

[1, 9, 36, 84, 126, 126, 84, 36, 9, 1]

The cost of each way

The table is n * n — fine for n up to a few thousand, hopeless at a hundred thousand.

Factorials with inverses are n to build and constant per question, which is why unit 14 finished where it did. Pick by the size of n, not by taste.

Try it yourself

The modulus is 1000, which is not prime. Which method still works?

  • Factorials with Fermat's inverse
  • Pascal's triangle
  • Neither
  • Both work the same

What does this print?

Python

rows = 5
table = [[1] * (r + 1) for r in range(rows)]
for r in range(2, rows):
    for c in range(1, r):
        table[r][c] = table[r - 1][c - 1] + table[r - 1][c]

print(sum(table[4]))

Answer them in the app

➖ Add Some, Take Some Back

How many numbers up to 100 divide by 2, 3 or 5?

50 divide by 2, 33 by 3, 20 by 5 — but that is 103, and there are only 100 numbers. Everything divisible by 6 has been counted twice.

So take the pairs off. That takes the multiples of 30 off three times having added them three times, so put them back. This is inclusion and exclusion.

Python

print(100 // 2, 100 // 3, 100 // 5)
print(100 // 6, 100 // 10, 100 // 15)
print(100 // 30)
print(50 + 33 + 20 - 16 - 10 - 6 + 3)

It prints

50 33 20
16 10 6
3
74

Every subset of the conditions, with a sign

Odd number of conditions: add. Even number: subtract. That is a loop over bitmasks, straight out of unit 3, and it handles ten conditions as easily as three.

bin(mask).count("1") counts the bits; the sign is whether that count is odd.

Python

limit = 100
divisors = [2, 3, 5]
total = 0

for mask in range(1, 1 << len(divisors)):
    product = 1
    bits = 0
    for i in range(len(divisors)):
        if (mask >> i) & 1:
            product *= divisors[i]
            bits += 1
    if bits % 2 == 1:
        total += limit // product
    else:
        total -= limit // product

print(total)

It prints

74

Checking it the slow way

For a hundred numbers you can simply count. Do it — a counting argument that disagrees with a loop is wrong, and it is always the argument.

For a limit of a thousand million the loop is impossible and the argument is instant, which is the point.

Python

count = 0
for n in range(1, 101):
    if n % 2 == 0 or n % 3 == 0 or n % 5 == 0:
        count += 1

print(count)

It prints

74

Try it yourself

Why is the count for "divisible by 2 or 3" not 50 + 33?

  • Because 100 is not divisible by 3
  • The multiples of 6 have been counted twice
  • Because 2 and 3 are prime
  • It is 50 + 33

What does this print?

Python

limit = 30
print(limit // 2 + limit // 3 - limit // 6)

Answer them in the app

🏆 Catalan Numbers

How many ways can brackets be balanced?

With three pairs there are five: ((())), (()()), (())(), ()(()), ()()().

That 5 is a Catalan number, and the same sequence — 1, 1, 2, 5, 14, 42 — counts mountain ranges, binary trees, and ways to cut a polygon into triangles.

Python

def balanced(open_left, close_left, so_far, found):
    if open_left == 0 and close_left == 0:
        found.append(so_far)
        return
    if open_left > 0:
        balanced(open_left - 1, close_left, so_far + "(", found)
    if close_left > open_left:
        balanced(open_left, close_left - 1, so_far + ")", found)

found = []
balanced(3, 3, "", found)
print(found)
print(len(found))

It prints

['((()))', '(()())', '(())()', '()(())', '()()()']
5

Split on where the first bracket closes

The opening bracket must close somewhere. Inside it is a balanced string, and after it is a balanced string, and every way of splitting the remaining pairs between the two gives a different answer.

So c[i] is the sum over j of c[j] * c[i - 1 - j]. Listing them all would be hopeless by 20 pairs; this loop is not.

Python

def catalan(n):
    c = [0] * (n + 1)
    c[0] = 1
    for i in range(1, n + 1):
        for j in range(i):
            c[i] += c[j] * c[i - 1 - j]
    return c

print(catalan(10))

It prints

[1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796]

And there is a closed form

choose(2n, n) divided by n + 1 gives the same numbers. It counts every string of n opens and n closes and then throws away the ones that go negative somewhere.

Under a modulus, that division needs the inverse from unit 14 — which is the moment the whole chain of this unit and the last one clicks together.

Python

def choose(n, k):
    result = 1
    for i in range(k):
        result = result * (n - i) // (i + 1)
    return result

print([choose(2 * n, n) // (n + 1) for n in range(11)])

It prints

[1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796]

Two ways to the same numbers is the whole trick

The loop and the formula agree on all eleven values, and the brute force agrees on the small ones. Three routes, one answer.

That is how you check a counting argument you are not sure of: build the small cases by force, and see whether your clever formula says the same thing.

Try it yourself

How many ways are there to balance 4 pairs of brackets?

  • 8
  • 14
  • 16
  • 24

What does this print?

Python

c = [0] * 6
c[0] = 1
for i in range(1, 6):
    for j in range(i):
        c[i] += c[j] * c[i - 1 - j]

print(c[5])

Answer them in the app