My Intro to Generating Functions: Let Coefficients Be Answers
Built from the original notes, restoring ordinary generating functions, exponential generating functions, modeling intuition, and several typical problems.
Coverage#
Generating functions section from original notesDifference between ordinary generating functions / exponential generating functionsModeling organization for HDU 2152 / HDU 1085 / HDU 1521Relationship with DP, convolution, and polynomials
Remember the Core Idea First#
The most useful aspect of generating functions isn’t memorizing a bunch of expansions, but rather:
- The exponent records “how many things were chosen”;
- The coefficient records “number of ways / weight / number of permutations”;
- Multiplication combines “several independent choices” together.
So I prefer to understand it as a way of putting counting problems into coefficients.
If the following signals appear in a problem, you should usually think of generating functions:
- Each type of item can be chosen several times;
- Only care about “how many were chosen in total / what’s the sum”;
- When combining multiple independent choices, it’s essentially convolution;
- Whether order matters directly determines whether to use ordinary or exponential generating functions.
Ordinary Generating Functions#
The original notes called it “mother function”, but I’ll use the more common competitive programming term OGF here.
Form:
Where records “the scale is ”, and coefficient records “number of ways / weight when scale is ”.
Three Basic Properties from Original Notes#
- Addition/Subtraction:
- Multiplication:
- So multiplication corresponds to convolution.
This is why generating functions are often paired with DP: many “first types of items making sum ” transitions are essentially multiplying the previous polynomial by “the choice formula this type of item can provide”.
Collect common forms of ordinary generating functions
These formulas will appear repeatedly:
- An item can be chosen times:
- An item can be chosen at most times:
- An item’s “weight / cost” is , can be chosen times:
- An item’s “weight / cost” is , can be chosen at most times:
When actually solving problems, the key isn’t memorizing formulas, but first thinking clearly:
- What exactly is the exponent recording;
- What exactly is the coefficient recording;
- What bracket should each type of item’s choice space be written as.
Understanding from Original Notes: Exponent is Count, Coefficient is Combination Number#
I think this expression is very memorable:
- The exponent is “how many were chosen in total / what’s the total weight”;
- The coefficient is the number of ways to reach this exponent.
Therefore, multiset combination number problems are particularly suitable for directly writing OGF.
Problem 1: HDU 2152 - Fruit#
The problem can be compressed into one sentence:
- There are types of items;
- Type must choose items;
- Find the number of ways to choose items in total.
Modeling#
Let type actually choose items, then:
So type item contributes a polynomial:
All ways correspond to:
What we want is the coefficient of .
Why This Problem is Completely Equivalent to DP#
If writing DP, usually set:
dp[j]represents the number of ways to make total when processing up to several types of items.
Adding type is equivalent to enumerating how many of this type to take, then convolving the coefficients in. This is the same as multiplying the current polynomial by .
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
while (cin >> n >> m) {
vector<int> l(n + 1), r(n + 1);
for (int i = 1; i <= n; ++i) {
cin >> l[i] >> r[i];
}
vector<int> dp(m + 1), ndp(m + 1);
dp[0] = 1;
for (int i = 1; i <= n; ++i) {
fill(ndp.begin(), ndp.end(), 0);
for (int used = 0; used <= m; ++used) {
if (!dp[used]) continue;
for (int take = l[i]; take <= r[i] && used + take <= m; ++take) {
ndp[used + take] += dp[used];
}
}
dp.swap(ndp);
}
cout << dp[m] << '\\n';
}
return 0;
}cppProblem 2: HDU 1085 - Holding Bin-Laden Captive!#
The core modeling from original notes is:
- There are coins with denominations ;
- Type coin has pieces;
- Find the minimum denomination that cannot be made.
Modeling#
Three types of coins correspond to three terms:
After expansion:
- If coefficient of is non-zero, it means amount is reachable;
- The first position with zero coefficient is the answer.
This problem is very suitable for experiencing “the exponent isn’t the count, but the target quantity”. In the previous problem, the exponent recorded “how many items were chosen”, here it records “the amount made”.
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int a1, a2, a3;
while (cin >> a1 >> a2 >> a3 && (a1 || a2 || a3)) {
int maxSum = a1 + 2 * a2 + 5 * a3;
vector<int> dp(maxSum + 1), ndp(maxSum + 1);
dp[0] = 1;
vector<pair<int, int>> items = {{1, a1}, {2, a2}, {5, a3}};
for (auto [w, cnt] : items) {
fill(ndp.begin(), ndp.end(), 0);
for (int s = 0; s <= maxSum; ++s) {
if (!dp[s]) continue;
for (int k = 0; k <= cnt && s + k * w <= maxSum; ++k) {
ndp[s + k * w] += dp[s];
}
}
dp.swap(ndp);
}
int ans = 0;
while (ans <= maxSum && dp[ans]) ++ans;
cout << ans << '\\n';
}
return 0;
}cppExponential Generating Functions#
Form:
From original notes, very well put:
- The EGF of is ;
- The EGF of is .
Why Divide by Here#
Because EGF is often used to handle labeled / order matters problems.
In OGF, convolution usually gives “add up the quantities” combination numbers; In EGF, multiplication naturally brings out combination number coefficients:
The here is “from positions, allocate to the first part, the rest to the second part”.
So it’s very suitable for handling:
- Order matters;
- Elements are labeled;
- Permutation numbers naturally appear.
Problem 3: HDU 1521 - Permutation Number Model#
From original notes:
- There are types of items;
- Type can choose at most items;
- Find the number of permutation ways when choosing exactly items.
If type chooses items, then the total number of permutations is:
This is exactly the classic flavor of EGF: write each type as
After multiplying together, take the coefficient before , then multiply back by .
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
vector<double> fac(11, 1.0);
for (int i = 1; i < (int)fac.size(); ++i) fac[i] = fac[i - 1] * i;
while (cin >> n >> m) {
vector<int> a(n + 1);
for (int i = 1; i <= n; ++i) cin >> a[i];
vector<double> dp(m + 1), ndp(m + 1);
dp[0] = 1.0;
for (int i = 1; i <= n; ++i) {
fill(ndp.begin(), ndp.end(), 0.0);
for (int used = 0; used <= m; ++used) {
if (dp[used] == 0.0) continue;
for (int take = 0; take <= a[i] && used + take <= m; ++take) {
ndp[used + take] += dp[used] / fac[take];
}
}
dp.swap(ndp);
}
cout << (long long)llround(dp[m] * fac[m]) << '\\n';
}
return 0;
}cppHow to Distinguish Between Ordinary and Exponential Generating Functions#
This is my most commonly used judgment criterion:
- Don’t look at order / look at total / look at combination numbers: Think OGF first.
- Look at order / look at permutation numbers / labeled: Think EGF first.
More specifically:
| Problem Flavor | More Common Choice |
|---|---|
| Each type of item choose how many, total make how much | Ordinary generating function |
| Only care about “sum / count”, order irrelevant | Ordinary generating function |
| Arrange into several positions, positions distinguished | Exponential generating function |
| Result naturally has | Exponential generating function |
What’s the Relationship with DP and Polynomials#
This part is best not separated.
Relationship with DP#
Many generating function problems, when implemented, are DP:
dp[j]maintains the coefficient of the -th term of the current polynomial;- Adding a type of item is multiplying a bracket;
- So the transition is essentially convolution.
Relationship with Polynomials#
Generating functions lean more toward modeling:
- What is the exponent;
- What is the coefficient;
- What brackets should the problem be written as.
Polynomials lean more toward computation:
- How to multiply these coefficients faster;
- How to change from convolution to ;
- How to do inverse, logarithm, exponent, square root.
So my understanding is:
- Generating functions tell you “why to put answers into coefficients”;
- Polynomials tell you “how to efficiently calculate after putting them in”.
Common Pitfalls When Solving Problems#
These pitfalls, I’m most likely to step on when solving problems
- Whether the exponent is recording “count” or “weight”, don’t mix them up.
- Don’t randomly apply OGF / EGF just because the formulas look similar; whether order matters is the first judgment criterion.
- If the problem has a constraint “at most items”, the bracket must be finite terms, not directly written as .
- After multiplying EGF, don’t forget to usually multiply back by at the end.
- Many problems are essentially generating function modeling + ordinary DP implementation, not necessarily needing to simplify the formula to the end.
How I Would Learn This Part Now#
If going through it again, I would follow this order:
- First thoroughly understand “coefficients are answers”;
- Use OGF to do several multiset counting problems;
- Use EGF to understand why permutation numbers naturally appear;
- Then look at convolution and polynomials, connecting “modeling” and “computation”.