Lesson 47 of 50 intermediate

Combinatorics — Counting Without Listing

How many ways? Let's count smart, not hard!

Open interactive version (quiz + challenge)

Real-world analogy

Imagine you're at an ice cream shop with 5 flavors and 3 toppings. Your friend starts listing every possible combo: 'Vanilla with sprinkles, vanilla with fudge, vanilla with gummies, chocolate with sprinkles…' — that takes forever! But you're smart. You say: '5 flavors × 3 toppings = 15 combos. Done!' That's combinatorics — the art of counting possibilities using clever math instead of writing out every single option. In CP, when a problem asks 'how many ways…?', combinatorics is your best friend.

What is it?

Combinatorics is the branch of mathematics that deals with counting, arranging, and choosing things. Instead of listing out every possibility (which could take years for large numbers), it gives us formulas and principles to calculate the count directly. In competitive programming, combinatorics problems are everywhere — from counting paths to choosing teams.

Real-world relevance

Combinatorics is used in everything from lottery probability (how many ticket combinations exist?) to computer science (how many different passwords can be made?), biology (how many ways can DNA bases combine?), and even card games (how many different poker hands are possible?). Every time you wonder 'how many ways can this happen?', you're thinking about combinatorics.

Key points

Code example

#include <bits/stdc++.h>
using namespace std;

// Compute factorial up to n
vector<long long> fact;
void precompute(int n) {
    fact.resize(n + 1);
    fact[0] = 1;
    for (int i = 1; i <= n; i++) {
        fact[i] = fact[i - 1] * i;
    }
}

// Compute nCr using precomputed factorials
long long nCr(int n, int r) {
    if (r < 0 || r > n) return 0;
    return fact[n] / (fact[r] * fact[n - r]);
}

int main() {
    precompute(20); // factorials up to 20!

    // Print Pascal's Triangle (first 8 rows)
    cout << "Pascal's Triangle:\n";
    for (int n = 0; n <= 7; n++) {
        for (int r = 0; r <= n; r++) {
            cout << nCr(n, r) << " ";
        }
        cout << "\n";
    }

    // Example: choosing 3 from 8
    cout << "\n8 choose 3 = " << nCr(8, 3) << "\n";

    // Permutation: 8P3 = 8! / 5!
    long long perm = fact[8] / fact[5];
    cout << "8 P 3 = " << perm << "\n";

    return 0;
}

Line-by-line walkthrough

  1. 1. We create a global vector called 'fact' to store precomputed factorial values.
  2. 2. The precompute function fills this vector: fact[0] = 1, and each fact[i] = fact[i-1] * i. This builds up 1!, 2!, 3!, etc.
  3. 3. The nCr function computes combinations using the formula n! / (r! × (n-r)!). It returns 0 if r is out of range.
  4. 4. In main, we call precompute(20) to calculate all factorials up to 20!. This is enough for our examples.
  5. 5. We print Pascal's Triangle by looping through rows 0 to 7. For each row n, we print nCr(n, r) for r from 0 to n.
  6. 6. We compute 8 choose 3 = 56, meaning there are 56 ways to pick 3 items from 8 when order doesn't matter.
  7. 7. For permutations (8P3), we compute 8! / 5! = 336. This counts arrangements where order matters.
  8. 8. The code keeps things simple with plain division. For CP contests with large numbers, you'd use modular arithmetic instead.

Spot the bug

#include <bits/stdc++.h>
using namespace std;

long long factorial(int n) {
    long long result = 1;
    for (int i = 1; i <= n; i++) {
        result *= i;
    }
    return result;
}

long long nCr(int n, int r) {
    return factorial(n) / factorial(r) / factorial(n - r);
}

int main() {
    // How many ways to choose 2 from 5?
    cout << "5C2 = " << nCr(5, 2) << "\n";
    // How many ways to choose 3 from 30?
    cout << "30C3 = " << nCr(30, 3) << "\n";
    // How many ways to choose 5 from 25?
    cout << "25C5 = " << nCr(25, 5) << "\n";
    return 0;
}
Need a hint?
The factorial function looks correct, but think about what happens with bigger numbers. What is 30!? Can a long long hold that value? Try computing 30! by hand (or calculator) and compare to the maximum value of a long long.
Show answer
The bug is integer overflow! 30! is about 2.65 × 10^32, which far exceeds the maximum long long value (~9.2 × 10^18). Even though the final answer of 30C3 = 4060 is small, the intermediate factorial(30) overflows and gives garbage. The fix is to either compute nCr iteratively (multiply and divide step by step to keep numbers small), use Pascal's Triangle, or use modular arithmetic for large values.

Explain like I'm 5

Imagine you're getting dressed and you have a drawer with 3 hats and 2 scarves. You want to know how many different hat-and-scarf looks you can make. Instead of trying every single combination on, you just count: 3 hats times 2 scarves equals 6 looks! Combinatorics is like being really good at counting things without having to actually do them all one by one. It's the lazy-but-smart way to figure out 'how many?'

Fun fact

The number of ways to shuffle a standard 52-card deck is 52!, which is roughly 8 × 10^67. That's more than the number of atoms in the observable universe! It means that every time you properly shuffle a deck, you're almost certainly holding a card arrangement that has NEVER existed before in human history.

Hands-on challenge

Build a program that takes n and r as input and prints: (1) nPr (permutations), (2) nCr (combinations), and (3) the full row n of Pascal's Triangle. Then solve this: a pizza shop has 10 toppings. How many different pizzas can you make if each pizza can have between 0 and 10 toppings? (Hint: think about each topping as a yes/no choice!) Bonus: compute nCr modulo 1,000,000,007 for n up to 200,000.

More resources

Open interactive version (quiz + challenge) ← Back to course: CP Zero to Hero