The basic counting rules, binomial coefficients and how to compute them, stars and bars with bounds, permutation tools (inverse, cycles, ranking), and the twelvefold way. Binomial coefficients mod a prime come from Comb and mint in Modular Arithmetic; identities are in Binomial Identities, inclusion–exclusion and derangements in Inclusion–Exclusion, Catalan numbers in Catalan and Lattice Paths, Stirling, Bell and partition numbers in Stirling, Bell and Partitions, symmetry counting in Burnside and Pólya, generating functions in Generating Functions.

RuleStatement
sum ruledisjoint alternatives add
product ruleindependent stages multiply (the number of choices at each stage must not depend on earlier choices)
bijectionequal counts if there is a bijection; the core of most clever counts
double countingcount one set two ways (rows vs columns, pairs vs incidences)
complementcount the opposite and subtract; with several conditions, inclusion–exclusion
pigeonhole objects in boxes: some box has : Pigeonhole Puzzles
ObjectCount
arrangements of distinct objects
ordered selections of of (permutations)
subsets of size (combinations, )
arrangements of a multiset with multiplicities multinomial
multisets of size from types (stars and bars)
words of length over letters
subsets of an -set

Pascal’s rule gives an table without inverses (any modulus); , , , and the binomial theorem .

Computing MethodCost
, any modulusPascal table
, prime modulus factorials and inverse factorials (Comb) once, per query
huge , small , prime modulus falling factorial
for a small prime Lucas’s theorem: Factorials mod p
composite modulusCRT over prime powers (Granville), or Pascal
exact values__int128 up to , unsigned long long up to
vector<vector<ll>> pascalTable(int n, ll mod) {           // C[i][j] mod any modulus, O(n^2)
    vector<vector<ll>> C(n + 1, vector<ll>(n + 1, 0));
    for (int i = 0; i <= n; i++) { C[i][0] = 1 % mod; for (int j = 1; j <= i; j++) C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % mod; }
    return C;
}
 
mint binomLarge(ll n, int r) {                            // C(n, r) for huge n, r < modulus
    if (r < 0 || n < r) return 0;
    mint num = 1, den = 1;
    for (int i = 0; i < r; i++) num *= mint(n - i), den *= mint(i + 1);
    return num / den;
}

Stars and bars, with bounds

identical items into distinguishable bins: with empty bins allowed, with every bin non-empty; equivalently non-negative (positive) integer solutions of . With upper bounds , inclusion–exclusion over the set of variables forced above their bound:

With equal bounds the sum collapses to terms; with individual bounds and up to , the binomial is a polynomial of degree in , so a meet in the middle over the two halves of (sorted by sum, with prefix sums of ) evaluates all pairs with at once: for .

mint countBoundedUniform(int n, ll s, ll l, ll r) {        // #x in [l, r]^n with sum s, O(n)
    if (n == 0) return s == 0 ? 1 : 0;
    s -= (ll)n * l; ll w = r - l + 1;
    if (s < 0 || w <= 0) return 0;
    mint res = 0, cnk = 1;                                 // C(n, j)
    for (int j = 0; j <= n; j++) {
        if (j) cnk = cnk * mint(n - j + 1) / mint(j);
        ll m = s - (ll)j * w; if (m < 0) break;
        mint term = cnk * binomLarge(m + n - 1, n - 1);
        res += j % 2 ? -term : term;
    }
    return res;
}
 
mint countBoundedBrute(const vector<ll>& a, ll s) {        // 0 <= x_i <= a_i, O(2^n n)
    int n = sz(a); if (n == 0) return s == 0 ? 1 : 0;
    mint res = 0;
    for (int mask = 0; mask < (1 << n); mask++) {
        ll m = s; for (int i = 0; i < n; i++) if (mask >> i & 1) m -= a[i] + 1;
        if (m < 0) continue;
        mint term = binomLarge(m + n - 1, n - 1);
        res += __builtin_popcount(mask) % 2 ? -term : term;
    }
    return res;
}
 
mint countBoundedIndividual(const vector<ll>& a, ll s) {   // meet in the middle, O(2^(n/2) n^2), n <= ~40
    int n = sz(a); if (n <= 1) return countBoundedBrute(a, s);
    vector<mint> c(n, 0); c[0] = 1; mint den = 1;          // P(m) = C(m + n - 1, n - 1) = sum_j c_j m^j
    for (int i = 1; i <= n - 1; i++) { for (int j = n - 1; j >= 0; j--) c[j] = c[j] * mint(i) + (j ? c[j - 1] : mint(0)); den *= mint(i); }
    for (auto& x : c) x /= den;
    auto subsetSums = [&](int from, int to) { vector<pair<ll, int>> v{{0, 1}}; for (int i = from; i < to; i++) { int k = sz(v); for (int j = 0; j < k; j++) v.push_back({v[j].first + a[i] + 1, -v[j].second}); } return v; };
    int h = n / 2; auto A = subsetSums(0, h), B = subsetSums(h, n);
    sort(all(B)); int nb = sz(B);
    vector<vector<mint>> pre(nb + 1, vector<mint>(n, 0)); // pre[k][j] = sum over the first k of B of sign * y^j
    for (int k = 0; k < nb; k++) { mint y = mint(B[k].first), p = mint(B[k].second); for (int j = 0; j < n; j++) pre[k + 1][j] = pre[k][j] + p, p *= y; }
    vector<vector<mint>> C(n, vector<mint>(n, 0)); for (int i = 0; i < n; i++) { C[i][0] = 1; for (int j = 1; j <= i; j++) C[i][j] = C[i - 1][j - 1] + C[i - 1][j]; }
    mint res = 0;
    for (auto [x, sx] : A) {
        ll t = s - x; if (t < 0) continue;
        int k = int(upper_bound(all(B), pair<ll, int>{t, INT_MAX}) - B.begin()); if (k == 0) continue;
        mint T = mint(t), total = 0; vector<mint> tp(n, 1); for (int j = 1; j < n; j++) tp[j] = tp[j - 1] * T;
        for (int j = 0; j < n; j++) {                      // sum_y sign_y P(t - y), expanding (t - y)^j
            mint inner = 0;
            for (int i = 0; i <= j; i++) { mint term = C[j][i] * tp[j - i] * pre[k][i]; inner += i % 2 ? -term : term; }
            total += c[j] * inner;
        }
        res += sx > 0 ? total : -total;
    }
    return res;
}

Permutation toolkit

vector<int> inversePermutation(const vector<int>& p) { vector<int> inv(sz(p)); for (int i = 0; i < sz(p); i++) inv[p[i]] = i; return inv; }
vector<int> compose(const vector<int>& f, const vector<int>& g) { vector<int> r(sz(f)); for (int i = 0; i < sz(f); i++) r[i] = f[g[i]]; return r; }   // f after g
 
vector<vector<int>> cycleDecomposition(const vector<int>& p) {   // includes fixed points as 1-cycles
    int n = sz(p); vector<char> seen(n, 0); vector<vector<int>> cycles;
    for (int i = 0; i < n; i++) if (!seen[i]) { vector<int> c; for (int j = i; !seen[j]; j = p[j]) seen[j] = 1, c.push_back(j); cycles.push_back(c); }
    return cycles;
}
 
struct OrderStatFenwick {                                  // the k-th smallest remaining value of {0..n-1}
    int n; vector<int> bit;
    OrderStatFenwick(int n) : n(n), bit(n + 1, 0) { for (int i = 0; i < n; i++) add(i, 1); }
    void add(int i, int d) { for (i++; i <= n; i += i & -i) bit[i] += d; }
    int prefixCount(int i) { int s = 0; for (; i > 0; i -= i & -i) s += bit[i]; return s; }   // remaining values < i
    int kth(int k) {                                       // 0-based
        int pos = 0, pw = 1; while (pw * 2 <= n) pw *= 2;
        for (; pw; pw >>= 1) if (pos + pw <= n && bit[pos + pw] <= k) pos += pw, k -= bit[pos];
        return pos;
    }
};
 
vector<int> kthPermutation(int n, ll k) {                  // 0-based k < n!, lexicographic; n <= 20
    vector<ll> f(n + 1, 1); for (int i = 1; i <= n; i++) f[i] = f[i - 1] * i;
    OrderStatFenwick fen(n); vector<int> res(n);
    for (int i = 0; i < n; i++) { ll q = k / f[n - 1 - i]; k %= f[n - 1 - i]; int v = fen.kth((int)q); res[i] = v; fen.add(v, -1); }
    return res;
}
 
ll permutationRank(const vector<int>& p) {                 // inverse of kthPermutation (Lehmer code)
    int n = sz(p); vector<ll> f(n + 1, 1); for (int i = 1; i <= n; i++) f[i] = f[i - 1] * i;
    OrderStatFenwick fen(n); ll rank = 0;
    for (int i = 0; i < n; i++) { rank += (ll)fen.prefixCount(p[i]) * f[n - 1 - i]; fen.add(p[i], -1); }
    return rank;
}

for inverse, composition and cycles; for ranking with the Fenwick tree (an array erase would cost per step). Ranks fit in long long for ; beyond that, compute the rank modulo a prime or as a big integer. Related: the order of a permutation is the lcm of its cycle lengths; its sign is ; sorting by swaps needs swaps (Permutation Constructions).

The twelvefold way

Count functions from balls to boxes, balls and boxes each labelled (L) or unlabelled (U), with arbitrary, injective ( per box) or surjective ( per box). = Stirling numbers of the second kind, = partitions of into exactly parts.

balls boxesanyinjectivesurjective
L L
U L
L U
U U

Recurrences and generating functions

A linear recurrence has characteristic equation ; distinct roots give with fixed by the initial values. In practice evaluate it for huge in by matrix power ( for Fibonacci) or by Bostan–Mori (Linear Recurrences, Matrix Exponentiation DP). Generating functions turn counting into algebra: Fibonacci , Catalan , stars and bars ; EGFs for labelled structures (permutations , derangements ); in contests they are used through polynomial multiplication (NTT mod 998244353) to compute whole sequences at once (Generating Functions).

Two classic problems

Monotone arrays (Codeforces 57C “Array”). Arrays of length with values in that are non-decreasing or non-increasing: . Kyoya and colored balls (Codeforces 553A). Balls of colours with balls of colour are drawn so that the last ball of colour comes before the last ball of colour : with .

mint monotoneArrays(int n, const Comb& cb) { return mint(2) * cb.C(2 * n - 1, n) - mint(n); }
mint kyoyaWays(const vector<int>& c, const Comb& cb) { mint res = 1; int S = 0; for (int x : c) { S += x; res *= cb.C(S - 1, x - 1); } return res; }