In combinatorics, graph enumeration is the branch that studies counting problems of graphs satisfying specific properties. Generating functions, Pólya's enumeration theorem, the symbolic method, and OEIS are the most important mathematical tools for solving such problems. Graph enumeration can be divided into labeled and unlabeled problems. In most cases1, the labeled version is simpler than its unlabeled counterpart, so we will first examine the counting of labeled problems.
Labeled Trees
That is, Cayley's formula. Refer to the article on Prüfer sequences. We can also obtain this result using the Kirchhoff matrix tree theorem, generating functions, and Lagrange's theorem.
Exercises
Labeled Connected Graphs
Example Problem "POJ 1737" Connected Graph
Example 「POJ 1737」Connected Graph
Problem summary: Find the number of labeled connected graphs with \(n\) vertices (\(n \leq 50\)).
This type of problem first appeared in the "Eight Problems for Men" series by Lou Jiao. Let \(g_n\) be the number of labeled graphs with \(n\) vertices, and \(c_n\) be the sequence to be found. A graph with \(n\) vertices has at most \(\binom{n}{2}\) edges. Each edge has two states (present or absent), and each state is independent, so \(g_n = 2^{\binom{n}{2}}\). We fix one vertex and enumerate the size of its connected component. Then we need to choose \(i-1\) vertices from the remaining \(n-1\) vertices to form a connected component. The vertices outside the connected component can be connected arbitrarily, leading to the following recurrence:
Rearranging gives an \(O(n^2)\) recurrence formula for the \(c_n\) sequence, which solves this problem.
Example Problem "2013 Training Team Assignment" Urban Planning
Example 「集训队作业 2013」城市规划
Problem summary: Find the number of labeled connected graphs with \(n\) vertices (\(n \leq 130000\)).
For sequence problems with larger data ranges, we often need to construct generating functions for these sequences to use efficient polynomial algorithms.
Method 1: Divide-and-Conquer FFT
The above recurrence can be seen as a form of self-convolution, so it can be computed using divide-and-conquer FFT with complexity \(O(n\log^2n)\).
Method 2: Polynomial Inverse
We expand the binomial coefficient in the recurrence and transform it:
Construct polynomials:
Substituting into the above gives \(CG = H\). Use polynomial inverse and then convolution to solve for \(C(x)\).
Method 3: Polynomial exp
Another approach uses the combinatorial meaning of polynomial exp in EGF. Let the EGFs of labeled connected graphs and simple graphs be \(C(x)\) and \(G(x)\), respectively. Then they satisfy:
Use polynomial ln to solve for \(C(x)\).
Labeled Eulerian Graphs, Bipartite Graphs
Example Problem "SPOJ KPGRAPHS" Counting Graphs
Example 「SPOJ KPGRAPHS」Counting Graphs
Problem summary: Find the number of labeled graphs with \(n\) vertices satisfying the following properties (\(n \leq 1000\)).
This problem has code length restrictions, so polynomial templates cannot be used directly, but generating functions can still help with analysis.
The connected graph problem was solved in the previous example. Consider Eulerian graphs. Note that the methods for counting connected graphs can be generalized to labeled connected graphs satisfying any property. For example, we can replace \(g_n\) in the connected graph recurrence with graphs where all vertices have even degree, and the resulting \(c_n\) will be Eulerian graphs.
We encapsulate the recurrence process from POJ 1737 into a connection function:
1 2 3 4 5 6 7 | |
The first two questions can be easily solved:
1 2 3 4 | |
Note that the connection recurrence process here is equivalent to taking the polynomial ln of its EGF. Similarly, we can write an inverse connection function, which is equivalent to taking the polynomial exp of its EGF.
1 2 3 4 5 6 7 | |
Now discuss labeled bipartite graph counting.
Let \(b_n\) be the number of bipartite graphs with n vertices, and \(g_n\) be the number of graphs with \(n\) vertices where vertices are 2-colored such that no edges exist between vertices of the same color. Enumerating the number of vertices of one color, we have2:
Next, we establish the relationship between \(g_n\) and \(b_n\) in two different ways.
Method 1: Double Counting
Let \(c_{n, k}\) be the number of bipartite graphs with k connected components. Then we have:
Comparing the two expressions for \(g_n\) and expanding:
We can obtain a recurrence for \(b_n\) with complexity \(O(n^3)\). Further using the inclusion-exclusion principle, it can be optimized to \(O(n^2)\) to pass this problem.
Method 2: Connection Recurrence
Methods 2 and 3 both use connected bipartite graphs \(b1_n\) A001832 to bridge \(g_n\) and \(b_n\).
Note that for each connected bipartite graph, there are exactly two different coloring methods, corresponding to two different connected 2-colored graphs. Thus, applying the connection operation to \(g_n\) yields a sequence exactly twice \(b1_n\), and \(b_n\) is obtained by applying the inverse connection operation to \(b1_n\).
Therefore:
1 2 3 4 5 6 7 | |
Both recurrence processes have complexity \(O(n^2)\) and can pass this problem.
Method 3: Polynomial exp
We can also understand the above recurrence using EGF.
Let \(G(x)\) be the EGF of \(g_n\), \(B1(x)\) be the EGF of \(b1_n\), and \(B(x)\) be the EGF of \(b_n\). Applying method 2, we have:
We can differentiate both sides and compare coefficients to obtain a recurrence formula suitable for coding, solving this problem. Note that methods 2 and 3 are essentially the same, and method 3 generally yields better time complexity.
$$ \begin{align} B_n^2 &= G \ 2B_nB_n' &= G' \end{align} ```
Reference Code
```cpp
include
using namespace std;
using LL = long long; constexpr int MOD = int(1e9) + 7;
// <<= '2. Number Theory .,//{ namespace NT { void INC(int& a, int b) { a += b; if (a >= MOD) a -= MOD; }
int sum(int a, int b) { a += b; if (a >= MOD) a -= MOD; return a; }
void DEC(int& a, int b) { a -= b; if (a < 0) a += MOD; }
int dff(int a, int b) { a -= b; if (a < 0) a += MOD; return a; }
void MUL(int& a, int b) { a = (LL)a * b % MOD; }
int pdt(int a, int b) { return (LL)a * b % MOD; }
int _I(int b) { int a = MOD, x1 = 0, x2 = 1, q; while (1) { q = a / b, a %= b; if (!a) return x2; DEC(x1, pdt(q, x2));
1 2 3 | |
} }
void DIV(int& a, int b) { MUL(a, _I(b)); }
int qtt(int a, int b) { return pdt(a, _I(b)); }
int pow(int a, LL b) { int c(1); while (b) { if (b & 1) MUL(c, a); MUL(a, a), b >>= 1; } return c; }
template
template
struct Int { int val;
operator int() const { return val; }
Int(int _val = 0) : val(_val) { val %= MOD; if (val < 0) val += MOD; }
Int(LL _val) : val(_val) { _val %= MOD; if (_val < 0) _val += MOD; val = _val; }
Int& operator+=(const int& rhs) { INC(val, rhs); return *this; }
Int operator+(const int& rhs) const { return sum(val, rhs); }
Int& operator-=(const int& rhs) { DEC(val, rhs); return *this; }
Int operator-(const int& rhs) const { return dff(val, rhs); }
Int& operator=(const int& rhs) { MUL(val, rhs); return this; }
Int operator*(const int& rhs) const { return pdt(val, rhs); }
Int& operator/=(const int& rhs) { DIV(val, rhs); return *this; }
Int operator/(const int& rhs) const { return qtt(val, rhs); }
Int operator-() const { return MOD - *this; } };
} // namespace NT
using namespace NT;
constexpr int N = int(1e3) + 9; Int binom[N][N], C[N], E[N], B[N], B1[N], G[N]; int n;
void ln(Int C[], Int G[]) { for (int i = 1; i <= n; ++i) { C[i] = G[i]; for (int j = 1; j <= i - 1; ++j) C[i] -= binom[i - 1][j - 1] * C[j] * G[i - j]; } }
void exp(Int G[], Int C[]) { for (int i = 1; i <= n; ++i) { G[i] = C[i]; for (int j = 1; j <= i - 1; ++j) G[i] += binom[i - 1][j - 1] * C[j] * G[i - j]; } }
int main() { cin.tie(nullptr)->sync_with_stdio(false);
n = 1000; for (int i = 0; i < n + 1; ++i) { binom[i][0] = 1; for (int j = 0; j < i; ++j) binom[i][j + 1] = binom[i - 1][j] + binom[i - 1][j + 1]; }
for (int i = 1; i <= n; ++i) G[i] = pow(2, binom[i][2]); ln(C, G); for (int i = 1; i <= n; ++i) G[i] = pow(2, binom[i - 1][2]); ln(E, G); for (int i = 1; i <= n; ++i) { G[i] = 0; for (int j = 0; j < i + 1; ++j) G[i] += binom[i][j] * pow(2, j * (i - j)); } ln(B1, G); for (int i = 1; i <= n; ++i) B1[i] /= 2; exp(B, B1);
int T; cin >> T; while (T--) { cin >> n; cout << "Connected: " << C[n] << '\n' << "Eulerian: " << E[n] << '\n' << "Bipartite: " << B[n] << "\n\n"; } }
```
Exercises
- UOJ Goodbye Jihai D. New Year's Chase
- BZOJ 3864. Big Friends and Multi-trees
- BZOJ 2863. Angry President
- Luogu P6295. Labeled DAG Counting
- LOJ 6569. Cactus Counting
- LOJ 6570. Caterpillar Counting
- Luogu P5434. Labeled Desert Counting
- Luogu P3343. [ZJOI2015] Gensokyo After Earthquake
- HDU 5279. YJC plays Minecraft
- Luogu P7364. Labeled Bipartite Graph Counting
- Luogu P5827. Vertex Biconnected Graph Counting
- Luogu P5827. Edge Biconnected Graph Counting
- Luogu P6596. How Many of Them
- Luogu U152448. Labeled Strongly Connected Graph Counting
- Project Euler 434. Rigid graphs
Riddell's Formula
The above usage of exp in EGF is sometimes called Riddell's formula for labeled graphs. The Euler transform of generating functions is sometimes called Riddell's formula for unlabeled graphs. The latter first appeared in Euler's study of partition numbers and also appears in the unbounded knapsack problem.
For a given sequence \(a_i\) and its corresponding OGF \(A(x)\), define the Euler transform of \(A(x)\) as:
Let the coefficients of \(\mathcal{E}(A(x))\) be \(b_i\), and define an auxiliary array \(c_i = \sum_{d|n} d a_d\). Then we have the recurrence formula:
Unlabeled Trees
Example Problem "SPOJ PT07D" Let us count 1 2 3
Example 「SPOJ PT07D」Let us count 1 2 3
Problem summary: Find the number of trees with n vertices satisfying the following properties.
Rooted Trees
The labeled case has been solved earlier. Now consider unlabeled rooted trees. Let their OGF be \(F(x)\). Applying the Euler transform, we get:
Extract the coefficients.
Unrooted Trees
Consider inclusion-exclusion. We subtract from the rooted tree count those where the root is not the center of gravity, and discuss parity of \(n\).
When \(n\) is odd:
There must exist a subtree of size \(\geq \left\lceil \frac{n}{2}\right\rceil\). Enumerating the size of this subtree:
When \(n\) is even:
Note that when there are two centers of gravity, the above process subtracts only once, so we need to further subtract:
Example Problem "Luogu P5900" Unlabeled Unrooted Tree Counting
Example 「Luogu P5900」无标号无根树计数
Problem summary: Find the number of unlabeled unrooted trees with n vertices (\(n \leq 200000\)).
For larger data ranges, the approach is similar: apply the Euler transform and use polynomial templates.
Unlabeled Simple Graphs
Example Problem "SGU 282. Isomorphism" Isomorphism
Example 「SGU 282. Isomorphism」Isomorphism
Problem summary: Find the number of ways to m-color the edges of an unlabeled complete graph with n vertices.
Note that when m = 2, the object is unlabeled simple graphs A000088. Apply Pólya's enumeration theorem:
Here, the permutation group \(G\) is the edge permutation group generated by the symmetric group on n vertices. However, the brute-force enumeration is \(O(n!)\), which cannot pass.
Consider classifying permutations by their cycle structure. Each cycle structure corresponds to an integer partition. We generate partitions using dfs(). The problem then reduces to finding for each partition \(p\) the number of corresponding permutations \(w(p)\) and the number of cycles \(c(p)\) in each class of permutations. The answer is:
Consider \(w(p)\). Each partition corresponds to a cyclic arrangement, and partitions of the same size are unordered. Thus:
Here \(q_i\) denotes the number of times a partition of size \(i\) appears in \(p\).
Consider \(c(p)\). The cycles on the vertex set affected by \(p\) are \(|p|\), but the problem involves edge coloring, so we also need to consider the edge permutation generated by the vertex permutation.
If an edge connects vertices in the same cycle of size \(p_i\), then the number of cycles generated by the edge is exactly \(\left\lfloor \frac{p_i}{2} \right\rfloor\).
If an edge connects vertices in two different cycles of sizes \(p_i\) and \(p_j\), the length of each cycle is \(\operatorname{lcm}(p_i,p_j)\), so the number of cycles generated by the edge is exactly \(\frac{p_i p_j}{\operatorname{lcm}(p_i,p_j)} = \gcd(p_i, p_j)\).
Reference Code
```cpp
include
include
using namespace std;
using LL = long long; int MOD = int(1e9) + 7;
namespace NT { void INC(int& a, int b) { a += b; if (a >= MOD) a -= MOD; }
int sum(int a, int b) { a += b; if (a >= MOD) a -= MOD; return a; }
void DEC(int& a, int b) { a -= b; if (a < 0) a += MOD; }
int dff(int a, int b) { a -= b; if (a < 0) a += MOD; return a; }
void MUL(int& a, int b) { a = (LL)a * b % MOD; }
int pdt(int a, int b) { return (LL)a * b % MOD; }
int _I(int b) { int a = MOD, x1 = 0, x2 = 1, q; while (1) { q = a / b, a %= b; if (!a) return x2; DEC(x1, pdt(q, x2));
1 2 3 | |
} }
void DIV(int& a, int b) { MUL(a, _I(b)); }
int qtt(int a, int b) { return pdt(a, _I(b)); }
int pow(int a, LL b) { int c(1); while (b) { if (b & 1) MUL(c, a); MUL(a, a), b >>= 1; } return c; }
template
template
struct Int { int val;
operator int() const { return val; }
Int(int _val = 0) : val(_val) { val %= MOD; if (val < 0) val += MOD; }
Int(LL _val) : val(_val) { _val %= MOD; if (_val < 0) _val += MOD; val = _val; }
Int& operator+=(const int& rhs) { INC(val, rhs); return *this; }
Int operator+(const int& rhs) const { return sum(val, rhs); }
Int& operator-=(const int& rhs) { DEC(val, rhs); return *this; }
Int operator-(const int& rhs) const { return dff(val, rhs); }
Int& operator=(const int& rhs) { MUL(val, rhs); return this; }
Int operator*(const int& rhs) const { return pdt(val, rhs); }
Int& operator/=(const int& rhs) { DIV(val, rhs); return *this; }
Int operator/(const int& rhs) const { return qtt(val, rhs); }
Int operator-() const { return MOD - *this; } };
} // namespace NT
using namespace NT;
constexpr int N = int(5e1) + 9;
Int Fact[N];
vector
void gen(int n = ::n, int s = 1) { if (!n) { Partition.push_back(cur); } else if (n >= s) { cur.push_back(s); gen(n - s, s); cur.pop_back(); gen(n, s + 1); } }
Int w(const vector
for (auto p : P) { z /= p; if (p != l) { z /= Fact[c]; l = p; c = 1; } else { ++c; } }
z /= Fact[c]; return z; }
int gcd(int x, int y) { return y ? gcd(y, x % y) : x; }
int c(const vector
int main() { cin >> n >> m >> MOD; Fact[0] = 1; for (int i = 1; i <= n; ++i) Fact[i] = Fact[i - 1] * i;
gen();
Int res = 0; for (auto P : Partition) { res += w(P) * pow(m, c(P)); } res /= Fact[n]; cout << res << endl; }
```
Exercises
- CodeForces 438 E. The Child and Binary Tree
- Luogu P5448. [THUPC2018] Good Graph Counting
- Luogu P5818. [JSOI2011] Isomer Counting
- Luogu P6597. Alkene Counting
- Luogu P6598. Alkane Counting
- Luogu P4128. [SHOI2006] Colored Graph
- Luogu P4727. [HNOI2009] Graph Isomorphism Counting
- AtCoder Beginner Contest 222 H. Binary Tree
- AtCoder Beginner Contest 284 Ex. Count Unlabeled Graphs
- Luogu P4708. Drawing
- Luogu P7592. Counting Trees (2021 CoE-II E)
- Luogu P5206. [WC2019] Counting Trees
References and Notes
- WC2015, Gu Yuzhou's exchange materials Graphical Enumeration
- WC2019, Generating Functions, Polynomial Algorithms, and Graph Counting
- Counting labeled graphs - Algorithms for Competitive Programming
- Graphical Enumeration Paperback, Frank Harary, Edgar M. Palmer
- The encyclopedia of integer sequences, N. J. A. Sloane, Simon Plouffe
- Combinatorial Problems and Exercises, László Lovász
- Graph Theory and Additive Combinatorics
-
Perhaps unlabeled binary trees are a counterexample. When the structure is simple, the corresponding permutation group is the identity group, in which case the labeled version can be directly obtained by multiplying by \(n!\). ↩
-
Pink Rabbit's blog tells us that this sequence can also be optimized using Chirp Z-Transform. ↩
本页面最近更新:,更新历史
发现错误?想一起完善? 在 GitHub 上编辑此页!
本页面贡献者:OI-wiki
本页面的全部内容在 CC BY-SA 4.0 和 SATA 协议之条款下提供,附加条款亦可能应用