Generating Functions - OGF
Key Points
- •An ordinary generating function (OGF) encodes a sequence () as a formal power series A(x) = .
- •Multiplying OGFs corresponds to convolution of sequences, which models combining independent choices in counting problems.
- •The geometric series 1/(1 - x) = 1 + x + + is the OGF for “any number of 1-sized items,” a key building block in combinatorics.
- •Coefficient extraction [] A(x) reads off , often via algebraic manipulation, partial fractions, or polynomial arithmetic.
- •Linear recurrences with constant coefficients have rational OGFs, enabling closed forms and fast nth-term computation.
- •In programming contests, fast convolution via NTT computes coefficients up to degree N in O(N N) time.
- •Products of simple OGFs solve coin-change and bounded-composition problems by turning combinatorics into polynomial multiplication.
- •Careful truncation, correct modulus arithmetic, and avoiding OGF/EGF mix-ups are crucial for correct implementations.
Prerequisites
- →Discrete convolution and polynomial multiplication — OGF multiplication equals convolution; efficient coefficient extraction relies on fast polynomial multiplication.
- →Modular arithmetic — Most competitive programming implementations use finite fields to avoid floating point issues in FFT/NTT.
- →Fast Fourier Transform / Number Theoretic Transform — These are essential to multiply polynomials in O(n log n) time for large degrees.
- →Linear recurrences and characteristic polynomials — Rational OGFs arise from such recurrences; fast nth-term algorithms depend on them.
- →Combinatorial modeling — Translating problems into products and quotients of simple OGFs is the heart of the method.
- →Partial fraction decomposition (optional) — Helps derive closed forms from rational OGFs when exact expressions are needed.
- →Binomial coefficients and factorial precomputation — Closed forms like [x^n](1 - x)^{-k} use combinations modulo a prime.
Detailed Explanation
Tap terms for definitions01Overview
An ordinary generating function (OGF) is a powerful tool that translates a sequence a_0, a_1, a_2, ... into an algebraic object: a formal power series A(x) = \sum_{n \ge 0} a_n x^n. We call it “ordinary” to distinguish it from exponential generating functions, which weight x^n by n!. With OGFs, hard counting problems often turn into algebraic manipulations: multiplying OGFs encodes combining choices; dividing by (1 - x) handles unbounded repetition; and rational functions emerge from linear recurrences. The magic lies in coefficient extraction: after building and simplifying A(x), we read [x^n] A(x) to get the answer for size n. For programming, this means we can compute the first N coefficients by polynomial arithmetic (convolution, division, inversion), which can be accelerated using FFT/NTT. For theory, OGFs provide elegant derivations of closed forms, like [x^n] (1 - x)^{-k} = C(n + k - 1, n). OGFs are especially effective for combinatorial classes where order within each component is fixed but the entire structure is counted by size. They connect directly to linear recurrences with constant coefficients, where the OGF is rational and coefficients can be computed quickly for large n. In short, OGFs turn counting into algebra and then back into numbers via coefficients.
02Intuition & Analogies
Think of a vending machine where each snack has a size (say, calorie count), and you want to know how many combinations reach a target size n. For a single snack type that comes in units of size 1, you can choose 0, 1, 2, ... items, so your options are encoded by 1 + x + x^2 + ...; the coefficient of x^k means “choose k items.” If you have two independent snack types, A and B, your total choices are formed by picking some amount from A and some from B; the number of ways to hit total size n is exactly the sum over splits i + (n - i), which corresponds to multiplying series: (sum a_i x^i)·(sum b_j x^j). The coefficient of x^n in the product adds up all pairs (i, n - i), just like counting all ways to split the target between A and B. This is why multiplication captures “combine independent choices.” Division corresponds to “undoing” a constraint or allowing repetition indefinitely. For example, 1/(1 - x) represents any number of 1-sized items, because expanding 1 + x + x^2 + ... enumerates all counts. Stacking such factors, like 1/(1 - x^d), models unlimited coins of denomination d, and the product over coin types accumulates combinations that meet a target sum. Once the algebraic expression reflects the problem, you only need to extract the coefficient of x^n to get the answer. Computationally, those coefficients come from polynomial operations. Fast convolution (NTT) is like using a very efficient calculator that multiplies long polynomials quickly, enabling you to handle large n under time limits.
03Formal Definition
04When to Use
Use OGFs when counts decompose into independent choices whose sizes add, because multiplication of OGFs mirrors this combination. Classic cases include coin change (unbounded or bounded), compositions subject to per-part constraints, tilings by fixed tiles, integer solutions to linear equations with bounds, and run-length constrained strings. When your counting problem leads to a linear recurrence with constant coefficients, OGFs provide a rational form that can be turned into an explicit formula or fast nth-term algorithms (Kitamasa, linear recurrences via polynomial methods). For computing the first N values of a sequence arising from such combinatorial models, polynomial arithmetic with NTT can compute coefficients up to degree N efficiently. When you can factor expressions into known series like 1/(1 - x), 1/(1 - x^d), or (1 - x)^{-k}, OGFs yield closed forms through identities like stars and bars. In contest settings, OGFs shine for problems that ask for number of ways to reach sums or lengths under component-wise constraints, especially when N is up to 2e5–1e6 and amortized O(N log N) is acceptable. They are less suitable when order among subcomponents creates permutations of elements (then EGFs may be better) or when dependencies are non-additive.
⚠️Common Mistakes
Common pitfalls include: (1) Mixing OGFs with EGFs: OGFs encode size-additive combinations without factorial weights; using EGF identities with OGFs leads to wrong coefficients. (2) Forgetting truncation: when computing up to degree N, all polynomial products should be truncated to degree N; otherwise runtime and memory explode and coefficients beyond N may wrap modulo unintentionally. (3) Misinterpreting multiplication: OGF multiplication counts combinations where sizes add; it does not permute labeled objects—do not apply it to problems where order among indistinguishable parts becomes significant in a labeled sense. (4) Incorrect modulus handling: during NTT-based convolution, using a modulus without a suitable primitive root or forgetting normalization after inverse NTT yields wrong answers. (5) Off-by-one in recurrences: when deriving A(x) for a recurrence, aligning indices and initial terms is crucial; a misplaced shift (e.g., starting sum at n = k rather than 0) changes P(x). (6) Assuming convergence properties: OGFs are formal; radius of convergence is irrelevant for coefficient identity in combinatorics—do not justify steps via analytic convergence unless you are in analytic combinatorics. (7) Division by non-unit series: formal division A(x)/Q(x) requires Q(0) to be invertible (nonzero in the ring); forgetting to ensure Q(0) = 1 (or invertible) makes the recurrence undefined. (8) Conflating combinations vs permutations in coin change: the product \prod (1 + x^{d} + x^{2d} + ...) counts multisets (order doesn’t matter); do not count ordered sequences unless you model that separately (e.g., with a different recurrence or composition scheme).
Key Formulas
OGF Definition
Explanation: This defines the ordinary generating function for a sequence (). Each coefficient of is exactly .
Cauchy Product
Explanation: Multiplying OGFs corresponds to discrete convolution of their coefficients. This models combining independent choices whose sizes add.
Geometric Series
Explanation: This series counts using any number of 1-sized parts. It is the building block for many OGFs with unbounded repetition.
Coefficient Extraction
Explanation: The bracket operator returns the coefficient of . It is linear, making algebraic manipulations translate into counting results.
Rational OGF for Recurrences
Explanation: Sequences satisfying linear recurrences with constant coefficients have rational OGFs, where Q encodes the recurrence. Initial conditions determine P.
Stars and Bars via OGF
Explanation: This gives a closed form for distributing n indistinguishable balls into k boxes. The binomial coefficient counts weak compositions.
Generalized Binomial Series
Explanation: Extends stars and bars beyond integers , using generalized binomial coefficients. Useful for partial fractions and asymptotics.
Coefficient Recurrence from Rational OGF
Explanation: Comparing coefficients in A(x)Q(x) = P(x) yields a recurrence to compute using previous terms and coefficients from Q.
Convolution Complexity
Explanation: Fast polynomial multiplication uses FFT/NTT to compute coefficients efficiently, critical for large N computations.
Partial Fractions to Terms
Explanation: Decomposing a rational OGF into poles shows that coefficients are linear combinations of terms ^n times polynomials in n. Useful for closed forms.
Complexity Analysis
Code Examples
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 // NTT for mod 998244353 (primitive root 3) 5 struct NTT { 6 static constexpr uint32_t MOD = 998244353; 7 static constexpr uint32_t G = 3; 8 9 uint32_t mod_pow(uint32_t a, uint32_t e) const { 10 uint64_t r = 1, x = a; 11 while (e) { 12 if (e & 1) r = r * x % MOD; 13 x = x * x % MOD; 14 e >>= 1; 15 } 16 return (uint32_t)r; 17 } 18 19 void ntt(vector<uint32_t> &a, bool invert) const { 20 int n = (int)a.size(); 21 static vector<int> rev; 22 static vector<uint32_t> roots{0, 1}; 23 if ((int)rev.size() != n) { 24 int k = __builtin_ctz(n); 25 rev.assign(n, 0); 26 for (int i = 0; i < n; i++) { 27 rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (k - 1)); 28 } 29 } 30 if ((int)roots.size() < n) { 31 int k = __builtin_ctz(roots.size()); 32 roots.resize(n); 33 while ((1 << k) < n) { 34 uint32_t z = mod_pow(G, (MOD - 1) >> (k + 1)); 35 for (int i = 1 << (k - 1); i < (1 << k); i++) { 36 roots[2 * i] = roots[i]; 37 roots[2 * i + 1] = (uint64_t)roots[i] * z % MOD; 38 } 39 k++; 40 } 41 } 42 for (int i = 0; i < n; i++) if (i < rev[i]) swap(a[i], a[rev[i]]); 43 for (int len = 1; len < n; len <<= 1) { 44 for (int i = 0; i < n; i += 2 * len) { 45 for (int j = 0; j < len; j++) { 46 uint32_t z = roots[len + j]; 47 uint32_t u = a[i + j]; 48 uint32_t v = (uint64_t)a[i + j + len] * z % MOD; 49 uint32_t x = u + v; 50 a[i + j] = (x >= MOD ? x - MOD : x); 51 a[i + j + len] = (u + MOD - v); 52 if (a[i + j + len] >= MOD) a[i + j + len] -= MOD; 53 } 54 } 55 } 56 if (invert) { 57 reverse(a.begin() + 1, a.end()); 58 uint32_t inv_n = mod_pow(n, MOD - 2); 59 for (int i = 0; i < n; i++) a[i] = (uint64_t)a[i] * inv_n % MOD; 60 } 61 } 62 63 vector<uint32_t> multiply(vector<uint32_t> a, vector<uint32_t> b, int need = -1) const { 64 if (a.empty() || b.empty()) return {}; 65 int n = 1, as = (int)a.size(), bs = (int)b.size(); 66 int tot = as + bs - 1; 67 if (need != -1) tot = min(tot, need); 68 while (n < as + bs - 1) n <<= 1; 69 a.resize(n); b.resize(n); 70 ntt(a, false); ntt(b, false); 71 for (int i = 0; i < n; i++) a[i] = (uint64_t)a[i] * b[i] % MOD; 72 ntt(a, true); 73 a.resize(as + bs - 1); 74 if (need != -1 && (int)a.size() > need) a.resize(need); 75 return a; 76 } 77 } ntt; 78 79 // Multiply a list of polynomials using divide-and-conquer; truncate to degree <= max_deg 80 vector<uint32_t> multiply_all(vector<vector<uint32_t>> &polys, int l, int r, int max_deg) { 81 if (l + 1 == r) { 82 auto res = polys[l]; 83 if ((int)res.size() > max_deg + 1) res.resize(max_deg + 1); 84 return res; 85 } 86 int m = (l + r) / 2; 87 auto L = multiply_all(polys, l, m, max_deg); 88 auto R = multiply_all(polys, m, r, max_deg); 89 auto P = ntt.multiply(L, R, max_deg + 1); 90 if ((int)P.size() > max_deg + 1) P.resize(max_deg + 1); 91 return P; 92 } 93 94 int main() { 95 ios::sync_with_stdio(false); 96 cin.tie(nullptr); 97 98 // Problem: Given coin denominations d_1..d_m (unbounded), count the number of multisets summing to S. 99 // OGF: F(x) = prod_{i=1..m} (1 + x^{d_i} + x^{2 d_i} + ...), answer is [x^S] F(x). 100 101 int m, S; 102 cin >> m >> S; 103 vector<int> d(m); 104 for (int i = 0; i < m; i++) cin >> d[i]; 105 106 vector<vector<uint32_t>> polys; 107 polys.reserve(m); 108 for (int i = 0; i < m; i++) { 109 vector<uint32_t> p(S + 1, 0); 110 for (int e = 0; e <= S; e += d[i]) p[e] = 1; // 1 + x^{d} + x^{2d} + ... up to degree S 111 polys.push_back(move(p)); 112 } 113 114 auto res = multiply_all(polys, 0, (int)polys.size(), S); 115 uint32_t ans = (S < (int)res.size() ? res[S] : 0); 116 cout << ans << "\n"; 117 return 0; 118 } 119
We encode each coin type i by the geometric series 1 + x^{d_i} + x^{2 d_i} + ... (truncated at degree S). The product of these polynomials has coefficient of x^S equal to the number of multisets of coins summing to S. We perform the product using an NTT-based divide-and-conquer to achieve near O(S log S) per level and avoid quadratic blow-up. The modulus 998244353 ensures a suitable primitive root for NTT.
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 // Compute nth term of k-term linear recurrence: 5 // a_n = c1*a_{n-1} + c2*a_{n-2} + ... + ck*a_{n-k} 6 // given initial terms a_0..a_{k-1}. Uses Kitamasa O(k^2 log n). 7 8 using int64 = long long; 9 const int64 MOD = 1000000007LL; 10 11 vector<int64> combine(const vector<int64>& a, const vector<int64>& b, const vector<int64>& coef) { 12 int k = (int)coef.size(); 13 vector<int64> t(2*k - 1, 0); 14 for (int i = 0; i < k; i++) 15 for (int j = 0; j < k; j++) 16 t[i + j] = (t[i + j] + a[i] * b[j]) % MOD; 17 for (int i = 2*k - 2; i >= k; i--) { 18 for (int j = 1; j <= k; j++) { 19 t[i - j] = (t[i - j] + t[i] * coef[j - 1]) % MOD; 20 } 21 } 22 t.resize(k); 23 return t; 24 } 25 26 int64 nth_term(vector<int64> init, vector<int64> coef, long long n) { 27 int k = (int)coef.size(); 28 if (n < (long long)init.size()) return (init[n] % MOD + MOD) % MOD; 29 vector<int64> pol(k), e(pol); // pol encodes current linear combination, e is like x^1 30 pol[0] = 1; // x^0 31 e[1 % k] = 1; // x^1 32 long long m = n; 33 while (m) { 34 if (m & 1) pol = combine(pol, e, coef); 35 e = combine(e, e, coef); 36 m >>= 1; 37 } 38 int64 ans = 0; 39 for (int i = 0; i < k; i++) ans = (ans + pol[i] * init[i]) % MOD; 40 return (ans + MOD) % MOD; 41 } 42 43 int main() { 44 ios::sync_with_stdio(false); 45 cin.tie(nullptr); 46 47 // Example: Fibonacci F_0=0, F_1=1, F_n = F_{n-1} + F_{n-2} 48 // OGF A(x) = x / (1 - x - x^2), rational -> fast nth computation. 49 50 long long n; cin >> n; 51 vector<int64> init = {0, 1}; 52 vector<int64> coef = {1, 1}; // c1=1, c2=1 53 cout << nth_term(init, coef, n) << "\n"; 54 return 0; 55 } 56
A k-term linear recurrence leads to a rational OGF with denominator Q(x) = 1 - c1 x - ... - ck x^k. Kitamasa’s method exponentiates x modulo Q(x) to build coefficients expressing a_n as a linear combination of initial values. This computes a_n in O(k^2 log n) time and avoids building the entire sequence.
1 #include <bits/stdc++.h> 2 using namespace std; 3 using int64 = long long; 4 const int MOD = 1000000007; 5 6 int addmod(int a, int b){ a+=b; if(a>=MOD) a-=MOD; return a; } 7 int submod(int a, int b){ a-=b; if(a<0) a+=MOD; return a; } 8 int mulmod(long long a, long long b){ return int((a*b)%MOD); } 9 int modpow(int a, long long e){ long long r=1,x=a; while(e){ if(e&1) r=r*x%MOD; x=x*x%MOD; e>>=1; } return (int)r; } 10 11 int main(){ 12 ios::sync_with_stdio(false); 13 cin.tie(nullptr); 14 15 // Using [x^n] (1 - x)^{-k} = C(n + k - 1, n) (stars and bars) 16 int T; cin >> T; 17 vector<pair<int,int>> qs(T); 18 int maxn = 0, maxk = 0; 19 for(int i=0;i<T;i++){ cin >> qs[i].first >> qs[i].second; maxn=max(maxn, qs[i].first + qs[i].second - 1); } 20 // Precompute factorials up to maxN = max(n + k - 1) 21 int N = maxn; 22 vector<int> fact(N+1), ifact(N+1); 23 fact[0]=1; for(int i=1;i<=N;i++) fact[i]=mulmod(fact[i-1], i); 24 ifact[N]=modpow(fact[N], MOD-2); 25 for(int i=N;i>0;i--) ifact[i-1]=mulmod(ifact[i], i); 26 27 auto C = [&](int n, int r)->int{ 28 if(r<0||r>n) return 0; 29 return mulmod(fact[n], mulmod(ifact[r], ifact[n-r])); 30 }; 31 32 for(auto [n,k]: qs){ 33 // answer = C(n + k - 1, n) 34 cout << C(n + k - 1, n) << "\n"; 35 } 36 return 0; 37 } 38
The OGF (1 - x)^{-k} encodes distributing indistinguishable balls into k boxes (weak compositions). Coefficient extraction gives a closed form: [x^n](1 - x)^{-k} = C(n + k - 1, n). We precompute factorials and inverse factorials modulo a prime to answer queries in O(1).
1 #include <bits/stdc++.h> 2 using namespace std; 3 using int64 = long long; 4 const int MOD = 1000000007; 5 6 int addmod(int a, int b){ a+=b; if(a>=MOD) a-=MOD; return a; } 7 int submod(int a, int b){ a-=b; if(a<0) a+=MOD; return a; } 8 int mulmod(long long a, long long b){ return int((a*b)%MOD); } 9 int modpow(int a, long long e){ long long r=1,x=a; while(e){ if(e&1) r=r*x%MOD; x=x*x%MOD; e>>=1; } return (int)r; } 10 11 int main(){ 12 ios::sync_with_stdio(false); 13 cin.tie(nullptr); 14 15 // Given P and Q with Q(0)=1, compute A(x)=P(x)/Q(x) up to x^N coefficients. 16 // Coefficient relation: sum_{i=0}^{degQ} q_i a_{n-i} = p_n (with q_0=1), so 17 // a_n = p_n - sum_{i=1}^{min(n,degQ)} q_i * a_{n-i}. 18 19 int degP, degQ, N; // degrees and how many coefficients to output (0..N) 20 cin >> degP >> degQ >> N; 21 vector<int> P(degP+1), Q(degQ+1); 22 for (int i = 0; i <= degP; i++) cin >> P[i]; 23 for (int i = 0; i <= degQ; i++) cin >> Q[i]; 24 25 if (Q[0] != 1) { 26 // Normalize to make Q[0]=1: divide P and Q by Q[0] 27 int invq0 = modpow(Q[0], MOD-2); 28 for (auto &x : P) x = mulmod(x, invq0); 29 for (auto &x : Q) x = mulmod(x, invq0); 30 } 31 32 vector<int> A(N+1, 0); 33 for (int n = 0; n <= N; n++) { 34 long long val = (n <= degP ? P[n] : 0); 35 int lim = min(n, degQ); 36 for (int i = 1; i <= lim; i++) { 37 val -= 1LL * Q[i] * A[n - i] % MOD; 38 if (val < 0) val += MOD; 39 } 40 A[n] = (int)(val % MOD); 41 } 42 43 // Example input to test quickly: 44 // For Fibonacci, A(x) = x / (1 - x - x^2): degP=1, P=[0,1]; degQ=2, Q=[1, (MOD-1+1)%MOD, (MOD-1+1)%MOD] -> Q=[1,MOD-1,MOD-1] for -x - x^2. 45 46 for (int i = 0; i <= N; i++) cout << A[i] << (i==N?'\n':' '); 47 return 0; 48 } 49
For a rational OGF with Q(0) = 1, the identity A(x)Q(x) = P(x) yields a direct recurrence for a_n in terms of previous a’s and known p_n. This computes the first N coefficients in O(N · degQ) time. It’s the constructive version of the formal derivation used to solve recurrences via OGFs.