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.
| Rule | Statement |
|---|---|
| sum rule | disjoint alternatives add |
| product rule | independent stages multiply (the number of choices at each stage must not depend on earlier choices) |
| bijection | equal counts if there is a bijection; the core of most clever counts |
| double counting | count one set two ways (rows vs columns, pairs vs incidences) |
| complement | count the opposite and subtract; with several conditions, inclusion–exclusion |
| pigeonhole | objects in boxes: some box has : Pigeonhole Puzzles |
| Object | Count |
|---|---|
| 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 | Method | Cost |
|---|---|---|
| , any modulus | Pascal 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 modulus | CRT 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;
}Proof: the formulas, Pascal's rule and the binomial theorem
Permutations: the first position has choices, the next , and so on (product rule): . Combinations: each -subset gives ordered selections, so . Multinomial: the arrangements of distinct labelled copies map to one arrangement of the multiset (permute equal copies). Pascal: an -subset of either contains ( ways) or not (). Binomial theorem: expanding , a term chooses from of the factors and from the others, in ways. Large : is exact over the integers, and is invertible modulo a prime . Checked: the Pascal table against
CombandbinomLargeagainst exact values.
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;
}Proof: stars and bars and the bounded versions
Stars and bars. A distribution is a row of stars and bars (bin boundaries); every arrangement of these symbols is a distribution and is determined by the bar positions: . Non-empty bins: put one item in each first, then distribute freely: .
Upper bounds. Let be the solutions with . In substitute for : a bijection with non-negative solutions summing to , counted by stars and bars (0 if negative). Inclusion–exclusion over gives the formula. With equal bounds, after shifting by the term depends only on , giving copies. Meet in the middle. For , , a polynomial of degree (its coefficients are built by multiplying out the factors). Split over the two halves; with , and , the needed sum is (only contributes). Expanding , the inner sums are prefix sums over the sorted second half, found by binary search. Checked: all three against direct enumeration for small bounds, and uniform against individual with equal bounds.
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).
Proof: ranking permutations (the factorial number system)
Permutations starting with the -th smallest available value are preceded, in lexicographic order, by exactly permutations (those starting with a smaller value), and among those starting with it the rest is ranked recursively among the remaining values. Hence with = the number of remaining values smaller than (the Lehmer code), , and every such digit sequence occurs exactly once (mixed-radix representation):
kthPermutationdecodes the digits andpermutationRankencodes them. The Fenwick descent finds the -th remaining value by binary lifting on prefix counts. Checked: rank and unrank are inverse for all permutations of , and agree withnext_permutationorder.
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 boxes | any | injective | surjective |
|---|---|---|---|
| L L | |||
| U L | |||
| L U | |||
| U U |
Proof: the twelve entries
LL: each ball independently picks a box (); injective picks distinct boxes in order (); surjective functions are partitions of the balls into non-empty blocks () with the blocks assigned to distinct boxes (). UL: a multiset of size from boxes is stars and bars (); injective is a set of boxes; surjective first puts one ball in each box and distributes freely, . LU: unlabelled boxes forget which block is which, so a function is a set partition of the balls into at most blocks (), exactly blocks for surjective, and injective is possible in exactly one way iff . UU: only the multiset of box sizes remains, an integer partition of into at most parts (resp. exactly parts); injective again .
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; }Proof: the two classic counts
Monotone arrays. A non-decreasing array is a multiset of size from values: by stars and bars; non-increasing arrays are their reverses (same count); the constant arrays are both, so the union has . Kyoya. Process colours in order. With colours placed ( balls, in a valid order), the last ball overall among the first must be colour ; the other balls of colour go anywhere among the first positions: ways, independently of the earlier arrangement. Multiply. Both checked against brute force for small inputs.
Related notes
- Modular Arithmetic (
mint,Comb) - Binomial Identities
- Counting DP