生成函数 应用

引言

生成函数有普通型生成函数和指数型生成函数两种,其中普通型用的比较多。形式上说,普通型生成函数用于解决多重集的组合问题,而指数型母函数用于解决多重集的排列问题。

因水平有限,此博客涉及的均是生成函数的初级应用,用于解决相关的算法竞赛题目。


斐波那契函数的通项公式

考虑一个经典问题:
求斐波那契函数的通项。
此问题的结论了解斐波那契函数的人都知道,那就是:
F ( n ) = 1 5 ∗ [ ( 1 + 5 2 ) n − ( 1 − 5 2 ) n ] F(n) = \frac{1}{\sqrt5}*[(\frac{1+\sqrt5}{2})^n - (\frac{1-\sqrt5}{2})^n ] F(n)=5 1[(21+5 )n(215 )n]

此结论初看好像在算法竞赛中没什么用,因为对于求斐波那契函数第n项的值,存在一个经典的算法,那就是矩阵快速幂。而且因浮点数存在误差,故计算机并不能精确计算出该公式的值。

但通过对此公式的深入了解和推导证明,我们将对斐波那契函数有更深的理解,并将矩阵快速幂和生成函数两个算法进行联系。

下面我们试着来推导这个公式。

首先从熟悉的矩阵快速幂来入手。

由递推关系式,可以构造矩阵:

[ 0 1 1 1 ] ∗ [ F ( n − 1 ) F ( n ) ] = [ F ( n ) F ( n + 1 ) ] \begin{bmatrix} 0 &1 \\1 & 1\end{bmatrix} * \begin{bmatrix} F(n-1)\\ F(n)\end{bmatrix} = \begin{bmatrix} F(n ) \\F(n+1)\end{bmatrix} [0111][F(n1)F(n)]=[F(n)F(n+1)]

通过观察已知,这其实也是一个递推式,逐层递推易得:
(1) [ 0 1 1 1 ] n ∗ [ F ( 0 ) F ( 1 ) ] = [ F ( n ) F ( n + 1 ) ] \begin{bmatrix} 0 &1 \\1 & 1\end{bmatrix}^n* \begin{bmatrix} F(0)\\ F(1)\end{bmatrix} = \begin{bmatrix} F(n ) \\F(n+1)\end{bmatrix} \tag1 [0111]n[F(0)F(1)]=[F(n)F(n+1)](1)

假设:
M n = [ 0 1 1 1 ] n M^n= \begin{bmatrix} 0 &1 \\1 & 1\end{bmatrix}^n Mn=[0111]n

故问题的关键便在于如何求解 M n M^n Mn

在算法竞赛中,常用的手段是利用计算机的强大计算能力和快速幂的思想来求解。
但实际上,在数学层面还有一个更加常用的方法来求解方阵的n次方,那就是矩阵的相似对角化。
由定义:
M = Q A Q − 1 M = QAQ^{-1} M=QAQ1

M n = Q A Q − 1 ∗ Q A Q − 1 ∗ Q A Q − 1 ∗ . . . . M^n = QAQ^{-1} * QAQ^{-1} *QAQ^{-1}*.... Mn=QAQ1QAQ1QAQ1....
因:
Q ∗ Q − 1 = E Q*Q^{-1} = E QQ1=E

故推出:
(2) M n = Q A n Q − 1 M^n = QA^nQ^{-1}\tag2 Mn=QAnQ1(2)

下面我们便来尝试将M矩阵相似对角化:

d e t ( M − λ E ) = 0 = = > λ = 1 + 5 2 和 1 − 5 2 det(M - \lambda E) = 0 ==> \lambda = \frac{1+\sqrt5}{2} 和\frac{1-\sqrt5}{2} det(MλE)=0==>λ=21+5 215

λ \lambda λ代入 M − λ E M-\lambda E MλE 求出基础解系后推出
Q = [ 2 2 1 + 5 1 − 5 ] Q = \begin{bmatrix} 2 &2 \\1+\sqrt5 & 1-\sqrt5\end{bmatrix} Q=[21+5 215 ]

将上述矩阵代入(2)式,得:
M n = [ 2 2 1 + 5 1 − 5 ] ∗ [ ( 1 + 5 2 ) n 0 0 ( 1 − 5 2 ) n ] ∗ [ 2 2 1 + 5 1 − 5 ] − 1 M^n = \begin{bmatrix} 2 &2 \\1+\sqrt5 & 1-\sqrt5\end{bmatrix} * \begin{bmatrix} (\frac{1+\sqrt5}{2})^n & 0\\ 0&(\frac{1-\sqrt5}{2})^n\end{bmatrix} * \begin{bmatrix} 2 &2 \\1+\sqrt5 & 1-\sqrt5\end{bmatrix}^{-1} Mn=[21+5 215 ][(21+5 )n00(215 )n][21+5 215 ]1

然后将 M n M^n Mn代入(1)式,便可推出:
F ( n ) = 1 5 ∗ [ ( 1 + 5 2 ) n − ( 1 − 5 2 ) n ] F(n) = \frac{1}{\sqrt5}*[(\frac{1+\sqrt5}{2})^n - (\frac{1-\sqrt5}{2})^n] F(n)=5 1[(21+5 )n(215 )n]


当然本博客主要是讲解生成函数,所以接下来我们会通过生成函数的角度重新推导一遍该通项公式,但实质上两个方法是相似而又存在联系的。

生成函数我个人理解是通过代数的方法来解决组合数学的问题。

首先由生成函数的定义,可以得出斐波那契数列的生成函数为:
G ( x ) = x + x 2 + 2 x 3 + 3 x 4 + 5 x 5 + 8 x 6 + . . . G(x) = x + x^2 + 2x^3 + 3x^4 + 5x^5 + 8x^6 + ... G(x)=x+x2+2x3+3x4+5x5+8x6+...
下面考虑如何化简该无穷级数。

考虑G(x)的第n项 F ( n ) x n F(n)x^n F(n)xn
由递推关系式 F ( n ) = F ( n − 1 ) + F ( n − 2 ) F(n) = F(n-1) + F(n-2) F(n)=F(n1)+F(n2)
又因为G(x)的第n-1项和第n-2项分别是:
F ( n − 1 ) x n − 1 F(n-1)x^{n-1} F(n1)xn1 F ( n − 2 ) x n − 2 F(n-2)x^{n-2} F(n2)xn2
故第n-1项乘上 x x x加上第n-2项乘上 x 2 x^2 x2刚好便是第n项的值。

故推出:
x 2 G ( x ) + x G ( x ) = G ( x ) − x x^2G(x) + xG(x) = G(x) - x x2G(x)+xG(x)=G(x)x(减去多出来的第一项 x)
移项后得出:
(3) G ( x ) = x 1 − x − x 2 G(x) = \frac{x}{1-x-x^2}\tag3 G(x)=1xx2x(3)

接下来再将G(x)展开为幂级数,那么 x n x^n xn 的系数便是我们需要求出的通项公式了

因已知
1 1 − x = 1 + x + x 2 + x 3 + x 4 + . . . . \frac{1}{1-x} = 1 + x + x^2 +x^3 + x^4 + .... 1x1=1+x+x2+x3+x4+....
故我们尝试着将G(x)裂项展开:

利用待定系数法:
假设:
G ( x ) = a 1 − r x + b 1 − s x G(x) = \frac{a}{1-rx} + \frac{b}{1-sx} G(x)=1rxa+1sxb
化简得出:
G ( x ) = ( a + b ) − ( a s + b r ) x 1 − ( s + r ) x + s r x 2 G(x) = \frac{(a+b) - (as+br)x}{1-(s+r)x + srx^2} G(x)=1(s+r)x+srx2(a+b)(as+br)x
对比(3)式,解方程得出:
r = 1 + 5 2 s = 1 − 5 2 r = \frac{1+\sqrt5}{2} s = \frac{1-\sqrt5}{2} r=21+5 s=215

对比矩阵法,这个r和s恰好就是M矩阵的两个特征值

另外解得:
a = 1 5 b = − 1 5 a = \frac{1}{\sqrt5} b = -\frac{1}{\sqrt5} a=5 1b=5 1

综上所述:
G ( x ) = a 1 − r x + b 1 − s x = a ( 1 + r x + ( r x ) 2 + . . . ) + b ( 1 + s x + ( s x ) 2 + . . . ) G(x) = \frac{a}{1-rx} + \frac{b}{1-sx} = a(1+rx + (rx)^2 + ...) + b(1+sx + (sx)^2 + ...) G(x)=1rxa+1sxb=a(1+rx+(rx)2+...)+b(1+sx+(sx)2+...)

故第n项的系数为:
F ( n ) = a r n + b s n = 1 5 [ ( 1 + 5 2 ) n − ( 1 − 5 2 ) n ] F(n) = ar^n + bs^n = \frac{1}{\sqrt5}[(\frac{1+\sqrt5}{2})^n - (\frac{1-\sqrt5}{2})^n ] F(n)=arn+bsn=5 1[(21+5 )n(215 )n]


求组合方案数

(1)买水果

其实这类题的解法都很类似,主要是对于生成函数求解方案数原理的深刻理解。

才接触母函数或叫做生成函数时,很多人都会被x的各种次方吓住,其实我们构造指数型的生成函数, x n x^n xn只是一个记号。

比如我们考虑一个买水果的问题:

现有三种水果:苹果,梨子和西瓜。其中买苹果的数量必须为偶数个,梨子的数量必须为3的倍数,西瓜的数量不多于4个,问买n个水果,有多少种方案数?

首先考虑苹果,记 F 1 ( n ) F1(n) F1(n)为买n个苹果的方案数。
已知
F ( n ) = { 0 n % 2 = = 1 1 n % 2 = = 0 F(n) = \begin{cases} 0 & n \% 2==1 \\ 1 & n \% 2 == 0 \end{cases} F(n)={01n%2==1n%2==0

现在我们希望能方便地统一表示n的所有取值对应的方案数。
我们构建一个幂级数,其中 x n x^n xn的系数便是当买n个苹果的方案数,此处 x n x^n xn没有任何意义,仅仅只是一个标记,代表其系数的数学意义。

设该幂级数为 G 1 ( x ) G1(x) G1(x)
G 1 ( x ) = x 0 + x 2 + x 4 + x 6 . . . G1(x) = x^0 + x^2 + x^4 + x^6 ... G1(x)=x0+x2+x4+x6...
此时 G 1 ( x ) G1(x) G1(x)便称为生成函数或母函数。

同理,对于梨子:
G 2 ( x ) = x 0 + x 3 + x 6 + x 9 + . . . G2(x) = x^0 + x^3 + x^6 + x^9 + ... G2(x)=x0+x3+x6+x9+...

而对于西瓜:
G 3 ( x ) = x 0 + x 1 + x 2 + x 3 + x 4 G3(x) = x^0 + x^1 + x^2 + x^3 + x^4 G3(x)=x0+x1+x2+x3+x4

我们构建一个新的函数 G ′ ( x ) = G 1 ∗ G 2 ∗ G 3 G'(x) =G1*G2*G3 G(x)=G1G2G3
先直接说结论: G ′ ( x ) G'(x) G(x) x n x^n xn的系数便是我们所要求的买n个水果的方案数。

我们可以这么来理解这件事。
假如我们买3个水果,此时的方案数一定包含两个苹果和一个西瓜。
两个苹果的方案数对应在 G 1 G1 G1里面是 x 2 x^2 x2的系数。
一个西瓜的方案数对应在 G 3 G3 G3里面是 x x x的系数。
两个系数对应相乘便是买两个苹果和一个西瓜的方案数。同时 G 1 G1 G1 x 2 x^2 x2和G3的 x x x相乘便是 G ′ G' G中的 x 3 x^3 x3

例题 HDU 2152


代码:

#include
#include
#include
#include
using namespace std;
typedef long long ll;

const int A = 100 + 10;
int c1[A],c2[A];
int l[A],r[A];

int main(){
    int n,m;
    while(~scanf("%d%d",&n,&m)){
        for(int i=1 ;i<=n ;i++){
            scanf("%d%d",&l[i],&r[i]);
        }
        memset(c1,0,sizeof(c1));
        memset(c2,0,sizeof(c2));
        for(int i=l[1] ;i<=r[1]&&i<=m ;i++) c1[i] = 1;  //初始化
        for(int i=2 ;i<=n ;i++){                        //枚举其他水果
            for(int j=0 ;j<=m ;j++){                    //模拟多项式乘法过程     
                for(int k=l[i] ;k<=r[i] && j+k<=m ;k++){
                    c2[j+k] += c1[j];                  
                }
            }
            for(int j=0 ;j<=m ;j++){
                c1[j] = c2[j];
                c2[j] = 0;
            }
        }
        printf("%d\n",c1[m]);
    }
    return 0;
}

(2)砝码称重问题

考虑一个称重问题:

假设我们现在要称重一个质量为n的物体,现在有三个砝码,分别重为a1,a2,a3,砝码可放在天平的左侧或右侧。问称出这个质量为n的物体有多少种不同方案。

此问题跟上一个问题类似,我们同样可以构建三个生成函数:
G 1 ( x ) = x 0 + x − a 1 + x a 1 G1(x) = x^0 + x^{-a1} + x^{a1} G1(x)=x0+xa1+xa1
G 2 ( x ) = x 0 + x − a 2 + x a 2 G2(x) = x^0 + x^{-a2} + x^{a2} G2(x)=x0+xa2+xa2
G 3 ( x ) = x 0 + x − a 3 + x a 3 G3(x) = x^0 + x^{-a3} + x^{a3} G3(x)=x0+xa3+xa3
然后相乘之后求 x n x^n xn的系数就好了。

(3)整数的划分

整数的划分问题也是求方案数,下面举一个例子:

对于3,有3 = 1 + 1 + 1 = 1 + 2 = 3
故有三种划分方案。

对于求整数n的划分数
可以看成是有n个无区别的小球,放进任意个非空的盒子里,求方案数

此题也可以用生成函数来求解,我们来考虑整数1-n的贡献。
对于数字1:每次可以贡献1
G ( x ) = x 0 + x 1 + x 2 + . . . G(x) = x^0 + x^1 + x^2 + ... G(x)=x0+x1+x2+...
对于数字2:每次可以贡献2
G ( x ) = x 0 + x 2 + x 4 + . . . G(x) = x^0 + x^2 + x^4 + ... G(x)=x0+x2+x4+...
以此类推

然后将1-n所有数字的生成函数乘起来,求 x n x^n xn的系数即可。

当然,敏锐的人能够发现,当n很大时,计算量可以说是爆炸的。
所以实际上,这个经典问题可以考虑DP求解。

d p [ i ] [ j ] dp[i][j] dp[i][j]是将j个无区别的物品划分成不超过i组的方案数。
则$ dp[i][j] = dp[i][j-i] + dp[i-1][j]$
这个递推表达式的由来不是本博客的重点= = 有兴趣的可以自己百度

拓展:上述问题是看成无区别的物品划分,若是有区别的物品划分方案数,则是一个经典数论知识:Bell数。Bell数的生成函数G(x)为:
G ( x ) = e e x − 1 G(x) = e^{e^x} - 1 G(x)=eex1


(待更新)

参考资料:
知乎问答
百度百科
Matrix67博客
参考博客

你可能感兴趣的:(生成函数 应用)