MathAdvanced

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 multiplicationOGF multiplication equals convolution; efficient coefficient extraction relies on fast polynomial multiplication.
  • Modular arithmeticMost competitive programming implementations use finite fields to avoid floating point issues in FFT/NTT.
  • Fast Fourier Transform / Number Theoretic TransformThese are essential to multiply polynomials in O(n log n) time for large degrees.
  • Linear recurrences and characteristic polynomialsRational OGFs arise from such recurrences; fast nth-term algorithms depend on them.
  • Combinatorial modelingTranslating 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 precomputationClosed forms like [x^n](1 - x)^{-k} use combinations modulo a prime.

Detailed Explanation

Tap terms for definitions

01Overview

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

Given a sequence ()_{n 0} over a ring R, its ordinary generating function is the formal power series A(x) = , where x is an indeterminate. As a formal series, issues of convergence are ignored; equality is coefficient-wise. The Cauchy product of two series A(x) = and B(x) = is defined by A(x)B(x) = with = (discrete convolution). Coefficient extraction is the linear map []: R[[x]] R that returns the coefficient from A(x). If a sequence satisfies a homogeneous linear recurrence with constant coefficients + + for n k, then its OGF is rational: A(x) = P(x) / Q(x) where Q(x) = 1 - x - - and deg , determined by initial conditions. Conversely, any rational OGF corresponds to a sequence satisfying a linear recurrence with constant coefficients. The operator [] is linear and interacts well with algebra: [](A + B) = []A + []B, []( A) = []A, and [](A·B) = []A · []B.

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

Core operations with OGFs reduce to polynomial arithmetic. If you need the first N coefficients of a product A(x)B(x), naive convolution costs O(), which is too slow for N ≳ 2e4. Using NTT (Number Theoretic Transform) under a suitable prime modulus (e.g., 998244353) reduces this to O(N log N) time and O(N) extra memory, dominated by the transform buffers. When multiplying many polynomials (e.g., product over coin types), a divide-and-conquer multiplication tree with NTT achieves near-optimal O(N lo N) total time in practice, depending on degree distribution and truncation after each step to cap degree at the target. For rational OGFs A(x) = P(x)/Q(x) with Q(0) invertible, computing the first N coefficients via the recurrence from A(x)Q(x) = P(x) costs O(N · deg Q) time and O(deg Q) memory, which is linear in N for fixed-degree recurrences. For the nth term of a k-term recurrence where n is huge (n up to 10^18), Kitamasa’s method runs in O( log n) time and O(k) memory; NTT-based linear recurrences via polynomial exponentiation can reach O(k log k log n) but are more complex to implement. Factorial precomputation to support combinations C(a, b) over a modulus requires O(M) time and memory for M being the maximum needed n, with O(1) per query after precomputation. Always truncate intermediate polynomials to the maximum needed degree to keep both time and space within bounds.

Code Examples

Fast coefficient extraction via NTT: Unbounded Coin Change with Target Sum
1#include <bits/stdc++.h>
2using namespace std;
3
4// NTT for mod 998244353 (primitive root 3)
5struct 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
80vector<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
94int 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.

Time: O(S log S log m) approximately, due to divide-and-conquer levels with NTT per multiplicationSpace: O(S) for result plus O(S) auxiliary buffers during NTT
Solve linear recurrences (from rational OGF) via Kitamasa
1#include <bits/stdc++.h>
2using 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
8using int64 = long long;
9const int64 MOD = 1000000007LL;
10
11vector<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
26int64 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
43int 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.

Time: O(k^2 log n)Space: O(k)
Closed form from OGF: [x^n](1 - x)^{-k} via binomial coefficients
1#include <bits/stdc++.h>
2using namespace std;
3using int64 = long long;
4const int MOD = 1000000007;
5
6int addmod(int a, int b){ a+=b; if(a>=MOD) a-=MOD; return a; }
7int submod(int a, int b){ a-=b; if(a<0) a+=MOD; return a; }
8int mulmod(long long a, long long b){ return int((a*b)%MOD); }
9int 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
11int 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).

Time: O(N) preprocessing for N = max(n + k - 1), O(1) per querySpace: O(N)
Compute first N coefficients of a rational OGF P(x)/Q(x) with Q(0)=1
1#include <bits/stdc++.h>
2using namespace std;
3using int64 = long long;
4const int MOD = 1000000007;
5
6int addmod(int a, int b){ a+=b; if(a>=MOD) a-=MOD; return a; }
7int submod(int a, int b){ a-=b; if(a<0) a+=MOD; return a; }
8int mulmod(long long a, long long b){ return int((a*b)%MOD); }
9int 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
11int 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.

Time: O(N · degQ)Space: O(N)