关于生成函数
生成函数简介
一般生成函数
对于一个数列 \((f_i)_{i\ge 0}\)
其中 \(f_i\) 表示规模为 \(i\) 的特定组合对象的个数
定义 \((f_i)_i\) 的一般型生成函数为
我们把所有满足 \(F(x)\) 的要求的组合对象简记为 \(f\)-对象,共有 \(f_i\) 个规模为 \(i\) 的 \(f\)-对象。
定义生成函数的加法运算:
定义生成函数的乘法运算:
生成序列:
推广一下,我们考虑 \(F(x)^k\),表示我们关心的组合对象是由 \(k\) 个 \(f\)-对象构成的一个序列
新序列的规模是每个元素的规模之和。
在此情形下 \(F(x)^k\) 生成函数为:
可以看出,\(\frac{1}{1-F(x)}\) 就是 “由 0 个或多个 \(f\)-对象构成的有序序列” 的生成函数。
需要注意的是,这里我们假设 \(f_0 =0\),也即没有规模为 \(0\) 的 \(f\)-对象(否则,生成的序列里可以有任意多个规模为 0 的 \(f\)-对象,上式不良定义)。
生成集合:
在有的场合里我们也关心 “由若干个 \(f\)-对象构成的多重集” 的生成函数,这里一个集合的规模定义为集合中元素规模之和。
考虑对于每个 \(f\)-对象,枚举它在最后的多重集里出现了多少次,有生成函数:
因为总共有 \(f_i\) 个不同的规模为 \(i\) 的 \(f\)-对象,要对它们依次做决策,所对应的生成函数即为
最后,枚举 \(i\) 并求乘积,我们所求的生成函数即为
我们使用 ln-then-exp 技巧做一些处理,得到一个更清晰的式子:
相比之前错误想法得到的结果 \(\exp(F(x))\),这里的正确结果添加了形如 \(F(x^j)/j\) 的一系列修正项。
注. 另有一个基于 Polya 原理的推导方法,可以得出一样的结论。
注. 在网络上有些资料中把 \(F\to \exp\left( \sum_{j=1}^{\infty} F(x^j)/j\right)\) 这一操作称为 Euler 变换,我尚未找到此命名的原始出处。
例. 计算有 \(N\) 个节点的无标号有根树个数,这里 \(N\le 2\times 10^5\)。
解法. 设所求数列的生成函数为 \(F(x)\)。考虑生成一个有根树的过程:
- 首先固定一个根节点。
- 根节点连出若干个子树,子树之间不区分顺序(相当于多重集)。
由此容易看出,\(F\) 满足递归式
\[F(x) = x\cdot \exp\left( \sum_{j=1}^{\infty} \frac{F(x^j)}{j} \right). \]可以使用分治FFT(在网络上有人称为“半在线卷积”)解决上述问题。计算量为 \(O(N\log^2 N)\)。
也可以参考这个题目.
指数生成函数
指数生成函数常用于处理统计 \(n\) 个不同标号的元素可以产生的某种结构的数量的计数问题
例如 \(n\) 个点的带标号无向连通图个数、\(n\) 个元素构成的某种排列个数等
定义 \((f_i)_i\) 的指数型生成函数为:
我们把所有满足 \(F(x)\) 的要求的结构记为 \(f\)-结构,在 \(i\) 个不同的元素之间,可以建立 \(f_i\) 种不同的 \(f\)-结构。
定义其加法运算:
定义其乘法运算:
这两种运算同样有着非常直观的组合解释:
\(F+G\) 表示我们可以在 \(i\) 个元素之间建立 \(f\)-结构或者 \(g\)-结构的总方案数
\(F\cdot G\) 则是计算了把 \(n\) 个元素划分成两组,然后在两组中分别建立一个 \(f\)-结构和一个 \(g\)-结构的方案数
生成序列:
推广一下,我们考虑 \(F(x)^k\),它表示把 \(n\) 个元素划分成带标号(有序)的 \(k\) 组,并且在每组中建立一个 \(f\)-结构的方案数。
对 \(k\) 求和就得到 \(\frac{1}{1-F(x)}\),表示把 \(n\) 个元素划分成有序的若干组,并在每一组之中建立一个 \(f\)-结构。
生成集合:
有时我们只关心把元素划分成 \(k\) 组,但是这 \(k\) 组并不需要区分顺序。此时我们可以考虑 \(\frac{F(x)}{k!}\),它表示把 \(n\) 个元素划分成 \(k\) 组,并且在每组里建立一个 \(f\)-结构的方案数。
当 \(f_0 = 0\) (也即不存在建立在空集上的结构)的时候,\(\frac{F(x)}{k!}\) 的计数一定是不重不漏的(这是因为 \(n\) 个元素互相可以区分,无论它们被怎样划分成 \(k\) 组,同一个划分方案总是会在 \(F(x)^k\) 中计入 \(k!\) 遍)。我们对 \(k=0,1,\dots,\infty\) 求和,就得到
此时,\(G(x)\) 表示有 \(n\) 个不同的元素,把它们划分成若干组,并且在每组之中建立一个 \(f\)-结构的方案数。
例. 令 \(H(x)=\sum_n h_n \cdot x^n/n!\) 表示 \(n\) 个点的带标号无向图的指数生成函数,熟知 \(h_n = 2^{\binom{n}{2}}\)。令 \(F(x)=\sum_{n} f_n\cdot x^n/n!\) 表示 \(n\) 个点的带标号连通无向图的指数生成函数。因为每个无向图都可以唯一地划分为若干个连通块,我们得到 \(H(x) = \exp(F(x))\),也就是 \(F(x) = \ln H(x)\)。通过多项式求 ln,可以在 \(O(n\log n)\) 时间内计算出 \(F(x)\) 的前 \(n\) 项系数。
生成函数和递推
处理形如 \(P(x) / Q(x)\),\(\deg P < \deg Q\) 的形式幂级数的一种常用方法是递推。
我们令 \(A(x) = \frac{P(x)}{Q(x)}\),那么就有:
设 \(Q(x) = 1 + \sum_{i=1}^{\deg Q}\limits q_i x^i\), \(P(x)= \sum_{i=0}^{\deg P}\limits p_i x^i\),考虑第 \(n\) 项公式的系数,当 \(n > \deg P\) 时候就有
注意到这是一个线性递推的形式。当 \(n \le \deg P\) 时候,有:
不难发现通过选择合适的 \((p_i)_{0\le i\le \deg P}\),我们可以取到任意的初值 \((a_i:0< i < \deg Q)\).
\(A(x)Q(x) = P(x)\) 只能实现线性递推。如果我们有一个微分方程 \(A(x)Q_0(x)+ A'(x)Q_1(x) +\dots + A^{(t)}(x)Q_t(x) = P(x)\),那么我们就能构造一个包含指标 \(n\) 的递推(这种递推被称为整式递推)。卡特兰数的 \(O(1)\) 时间递推公式就是这个技巧的一个经典应用。
常见的几个生成函数形式
有必要熟练掌握以下形式幂级数的闭形式。
- \(\sum_{i=0}^{\infty}\limits x^i = \frac{1}{1-x}\).
- \(\sum_{i=0}^{\infty}\limits \binom{n+k-1}{k-1} x^i = \frac{1}{(1-x)^k}\).
- \(\sum_{i=1}^{\infty}\limits \frac{x^i}{i} = -\ln(1-x)\).
- \(\sum_{i=1}^{\infty}\limits \frac{x^i}{i!} = \exp(x)\).
- \(\sum_{i=0}^{\infty}\limits x^{2i} = \frac{1}{1-x^2}\), \(\sum_{i=0}^{\infty}\limits \frac{x^{2i}}{(2i)!} = \frac{e^x+e^{-x}}{2}\).
- (五边形数定理) \(\prod_{n=1}^{\infty}\limits (1-x^n) = \sum_{n=-\infty}^{\infty}\limits (-1)^n x^{\frac{n(3n+1)}{2}}\).
卡特兰数
基于递归式的定义:\(h_0 = 1\), \(h_n = \sum_{i=0}^{n-1}\limits h_i h_{n-i-1}, \forall n\ge 1\).
通项公式的定义:\(h_n = \binom{2n}{n}-\binom{2n}{n+1} = \frac{1}{n+1}\binom{2n}{n}\).
卡特兰数的生成函数:\(zC(z)^2 + 1 = C(z), C(0) = 1\) 的解.
卡特兰数的几种组合意义
- \(n+2\) 边形的不同的三角剖分方案数:固定一个多边形的一个边,考虑这个边所在的三角形的顶点,把问题划分成两个子问题。
- \(n\) 个元素按照顺序入栈,可能的出栈顺序数:第一个进栈的元素是第 \(i\) 个出栈,划分成 \(i-1\) 和 \(n-i\) 两个子问题。
- \(n\) 个
(
和 \(n\) 个)
的合法括号序列个数:枚举和第一个左括号匹配的右括号的位置,划分成两个子问题。 - \(n\) 个节点的无标号二叉树形态数:枚举左右子树的大小,划分成两个规模更小的子问题。
- 构造一个由 +1 和 -1 构成的长度为 \(2n\) 的序列,满足序列和为 0,且任意的前缀和非负。
- 一个 \(n\times n\) 的网格,从 \((0,0)\) 移动到 \((n,n)\),要求:第一步向右走且不穿越对角线的方案数。
组合推导
考虑第 5 个组合意义. 首先,任意构造一个和为 \(0\) 的 \(\pm 1\) 序列的方案数为 \(\binom{2n}{n}\),但是这样会算进去一些不合法的方案。对于任意一种不合法的方案 \(P = (a_1,\dots, a_{2n})\),考虑第一个 \(i\) 使得 \(\sum_{j=1}^{i}a_j = -1\),把 \(a_1\dots a_i\) 翻转符号,它对应了一个和为 \(2\) 的序列。可以证明“总和为 0 但是不合法的方案” 和 “总和为 2 的方案” 之间存在一个双射,因此我们知道不合法的方案总数为 \(\binom{2n}{n-1}\)。因此所求方案数为 \(\binom{2n}{n} - \binom{2n}{n+1}\)。
生成函数
令 \(C(z) = \sum_{i=0}^{\infty} h_i z^i\) 为卡特兰数的生成函数。那么根据递归式,我们可以写出 \(C(z) = z C(z)^2 + 1\) 以及 \(C(0) = h_0 = 1\)。
由 \(C=zC^2+1\) 我们可以解出 \(C(z) = \frac{1 \pm \sqrt{1-4z}}{2z}\)。由于 \(C(0) = 1\),分子上的符号取负。根据广义二项式定理,我们做以下推导:
其中第 5 行到第 6 行的推导中,我们用到了 \(2^n = \frac{2}{1}\frac{4}{2}\frac{6}{3}\cdots \frac{2n}{n}\).
注:有些文献里也会采用双阶乘记号,也即 \((2n)!! = (2n)(2n-2)\cdots 2\) 以及 \((2n-1)!!= (2n-1)(2n-3)\cdots 3\cdot 1\).
也可以用一种称为微分法的方法得到一个卡特兰数的递推公式。我们记 \(1-2zC(z) = \sqrt{P(z)}\),其中 \(P(z)\) 是一个度数为 \(1\) 的多项式。那么对 \(z\) 求导,就得到
整理一下,就得到
两边作用 \([z^n]\),得出
不难验证这个递推公式的正确性。
。