Combinatorics contents
Binomial Coefficients
Count subsets with C(n, k): Pascal's triangle, exact big-integer formulas, and O(1) queries modulo a prime with precomputed factorials.
Read first: Modular Multiplicative Inverse
The binomial coefficient is the number of ways to choose elements out of when the order doesn't matter. It also is the coefficient of in , hence the name.
with for or .
Properties
- Symmetry: (choosing the elements to take is the same as choosing those to leave).
- Pascal's rule: (the first element is either taken or not).
- Row sum: (the number of subsets).
- Hockey stick: .
- Vandermonde: .
- .
from math import comb, factorial
assert comb(5, 2) == 10 == factorial(5) // (factorial(2) * factorial(3))
assert comb(10, 3) == comb(10, 7)
assert comb(10, 4) == comb(9, 3) + comb(9, 4)
assert sum(comb(8, k) for k in range(9)) == 2 ** 8
assert sum(comb(i, 3) for i in range(3, 10)) == comb(10, 4)
assert sum(comb(4, k) * comb(5, 6 - k) for k in range(7)) == comb(9, 6)
assert comb(5, 7) == 0Calculation
Exact values
Python's math.comb(n, k) (Python 3.8+) returns the exact value, however large. For your own implementation, multiply and divide alternately and keep the integer exact at every step:
def binom(n, k):
if k < 0 or k > n:
return 0
k = min(k, n - k) # use symmetry: fewer steps
result = 1
for i in range(1, k + 1):
result = result * (n - k + i) // i # the result after step i is C(n-k+i, i): always an integer
return result
assert binom(5, 2) == 10 and binom(0, 0) == 1 and binom(52, 5) == 2_598_960
assert all(binom(n, k) == (comb(n, k) if 0 <= k <= n else 0) for n in range(30) for k in range(-1, n + 2))Because the partial products are integers, the divisions are exact. This takes .
Pascal's triangle
To get all values up to (for small ), build the triangle row by row using Pascal's rule, in :
def pascal(n):
rows = [[1]]
for i in range(1, n + 1):
prev = rows[-1]
rows.append([1] + [prev[j] + prev[j + 1] for j in range(i - 1)] + [1])
return rows
tri = pascal(6)
assert tri[4] == [1, 4, 6, 4, 1]
assert tri[6] == [1, 6, 15, 20, 15, 6, 1]
assert all(tri[n][k] == comb(n, k) for n in range(7) for k in range(n + 1))Modulo a prime in per query
Counting problems ask for the answer modulo a prime (often or ). Precompute the factorials and the inverse factorials once, using the modular inverse; then each costs two multiplications.
MOD = 1_000_000_007
class Binomial:
def __init__(self, limit, mod=MOD):
self.mod = mod
self.fact = [1] * (limit + 1)
for i in range(1, limit + 1):
self.fact[i] = self.fact[i - 1] * i % mod
self.inv_fact = [1] * (limit + 1)
self.inv_fact[limit] = pow(self.fact[limit], mod - 2, mod)
for i in range(limit, 0, -1): # (i-1)!^-1 = i * i!^-1
self.inv_fact[i - 1] = self.inv_fact[i] * i % mod
def C(self, n, k):
if k < 0 or k > n:
return 0
return self.fact[n] * self.inv_fact[k] % self.mod * self.inv_fact[n - k] % self.mod
B = Binomial(100_000)
assert B.C(5, 2) == 10
assert B.C(100_000, 50_000) == comb(100_000, 50_000) % MOD
assert B.C(10, 11) == 0Only one modular exponentiation is needed (for the top factorial); the inverse factorials for smaller arguments follow by multiplying up.
This requires ; otherwise is divisible by and has no inverse.
Lucas' theorem: large , small prime
When is a small prime but and can be huge, write both in base : , . Then
(and if any the coefficient is modulo ).
def lucas(n, k, p):
"""C(n, k) mod a small prime p, for arbitrarily large n and k."""
small = [1] * p
for i in range(1, p):
small[i] = small[i - 1] * i % p
inv = [pow(f, p - 2, p) for f in small]
result = 1
while n or k:
ni, ki = n % p, k % p
if ki > ni:
return 0
result = result * small[ni] * inv[ki] * inv[ni - ki] % p
n //= p
k //= p
return result
assert lucas(10, 3, 7) == comb(10, 3) % 7
assert all(lucas(n, k, 5) == comb(n, k) % 5 for n in range(60) for k in range(n + 1))
assert lucas(1000, 300, 13) == comb(1000, 300) % 13 # checked against the exact value
assert lucas(10 ** 18, 10 ** 9, 13) in range(13) # astronomically large n: still instantModulo a composite number
If the modulus is composite: factor it into prime powers and combine the residues with the Chinese Remainder Theorem. For a prime power one needs a generalization of Lucas (Granville's theorem). In Python it's often simpler to compute the exact math.comb(n, k) % m when is up to a few tens of thousands.
Which method?
| Situation | Use |
|---|---|
| one value, exact | math.comb(n, k) |
| many values, , modulo a big prime | factorials + inverse factorials |
| , all values | Pascal's triangle (works for any modulus) |
| huge , small prime modulus | Lucas' theorem |
Practice problems
- Codechef - Number of ways
- Codeforces - Curious Array
- LightOj - Necklaces
- HACKEREARTH: Binomial Coefficient
- SPOJ - Ada and Teams
- SPOJ - Greedy Walking
- UVa 13214 - The Robot's Grid
- SPOJ - Good Predictions
- SPOJ - Card Game
- SPOJ - Topper Rama Rao
- UVa 13184 - Counting Edges and Graphs
- Codeforces - Anton and School 2
- Codeforces - Bacterial Melee
- Codeforces - Points, Lines and Ready-made Titles
- SPOJ - The Ultimate Riddle
- CodeChef - Long Sandwich
- Codeforces - Placing Jinas