快樂地打牢基礎(13)——普通型母函數和指數型母函數的應用

母函數就是一列用來展示一串數字的掛衣架。 ——赫伯特·唯爾夫 。

一、普通型母函數


1.定義

對於任意數列a0,a1,a2...ana_0,a_1,a_2...a_n,用如下方法與一個函數聯繫起來:
G(x)=a0+a1x+a2x2+...+anxn G(x) = a_0+a_1x+a_2x^2+...+a_nx^n
則稱G(x)G(x)是數列的母函數(generating function)(也叫生成函數)。
其一般形式爲:
G(an;x)=i=1aixi \displaystyle G(a_n;x)=\sum_{i=1}a_ix^i

2.應用

組合數學的主要內容是計數,母函數是組合數學中的一個重要理論和工具。那麼它是怎麼應用的呢?

普通型母函數主要是解決求有限多重集的組合

設元素 a1,a1,,ana_{1},a_{1}, \cdot \cdot \cdot ,a_{n} 互不相同,從有限多重集{K1a1,K2a2,Knan}\left\{ K_{1} \cdot a_{1},K_{2} \cdot a_{2}, \cdot \cdot \cdot K_{n} \cdot a_{n} \right\}中選取 rr 個元素,至少存在一個Ki<rK_{i} < r 時,求其組合。

下面舉一個應用實例來理解這個問題:

現在我有兩個色子,每個色子有六個面,每個色子擲一次,問兩個色子投擲後加起來一共六點的情況有多少種?

我們數一數,根據加法原理:

  • 6 = 1 + 5 = 5 +1
  • 6= 2 + 4 = 4 + 2
  • 6 = 3 + 3

根據乘法原理

  • 第一次取 1,2,3,4,5 共五種方式
  • 由於第一次已經取好,所以第二次的取法是固定的 只有一種

綜上,加起來點數爲 6 一共是 五 種。

但是如果有 nn 個色子呢?顯然就很難這樣計算出來了

我們這時就需要母函數了。
我們可以 x,x2,x3,x4,x5,x6x,x^2,x^3,x^4,x^5,x^6 和 色子的 1,2,3,4,5,61,2,3,4,5,6點映射對應起來。

按照乘法原理,我們可以將兩次 色子的投擲看成是 兩個 多項式相乘。
例如 投擲 6 點
第一次 投的 是 4 點,第二次是 2 點。和 x4x2=x6x^4 x^2=x^6對應起來。

那麼第一次投擲可能取1,2,3,4,5,61,2,3,4,5,6點,這些情況是不可能同時發生的,那麼根據加法原理:
第一次投擲就可以和 多項式(x+x2+x3+x4+x5+x6)(x+x^2+x^3+x^4+x^5+x^6) 形成一個映射關係,xix^i就表示投擲出了 ii 點。
那麼兩次投擲的過程就能表示爲兩個多項式的乘積:
(x+x2+x3+x4+x5+x6)(x+x2+x3+x4+x5+x6)(x+x^2+x^3+x^4+x^5+x^6)*(x+x^2+x^3+x^4+x^5+x^6)
乘積的結果是
x2+2x3+3x4+4x5+5x6+6x7+5x8+4x9+3x10+2x11+x12x^2+2x^3+3x^4+4x^5+5x^6+6x^7+5x^8+4x^9+3x^{10}+2x^{11}+x^{12}
x6:x1x5+x2+x4+x3x3+x4x2+x5x1=5x6x^6:x^1x^5+x^2+x^4+x^3x^3+x^4x^2+x^5x^1=5x^6

我們發現 x6x^6的係數就是兩次投擲點數和爲 6 的方法數。

到此我們可以得到一個結論:

投擲 mm 粒色子時,加起來點數的綜合化等於nn的可能方式的數目爲:
G(x)=(x+x2+x3+x4+x5+x6)mG(x)=(x+x^2+x^3+x^4+x^5+x^6)^m
展開式中xnx^n的係數。

很明顯G(x)G(x)就是序列{1,2,3,4,5,6}\{1,2,3,4,5,6\}的一個母函數,這個例子也很好的說明了母函數的應用:

將計數問題映射成多項式的乘法運算來解決

HDU 1028 Ignatius and the Princess II
題意

給定一個數NN,問NN拆成若干個整數有多少中拆分方法?

思路
使用母函數進行映射
取 0 個11: x0x^0
取 1 個11: x1x^1
取 2 個11: x2x^2
11 對應的母函數: (1+x+x2+...+xk+...)(1+x+x^2+...+x^k+...)
22 對應的母函數: (1+x1+x4+...+x2k...)(1+x^1+x^4+...+x^{2k}...)
G(x)=(1+x+x2+...)(1+x2+x4+...)(1+x2+x4+...)(1+xN)G(x) = (1+x+x^2+...)(1+x^2+x^4+...)(1+x^2+x^4+...)(1+x^N)

xNx^N的係數就是 我們要求的答案。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#define ll long long
using namespace std;

const int N = 1000;
ll a[N],b[N];
int n;

void mother_fun()
{
    a[0] = 1;
    for(int i = 1; i <= n ; i++)
    {
        memset(b,0,sizeof(b));
        for(int j = 0; j * i <= n; j++)
        {
            for(int k = 0; k <= n; k++)
            {
                b[i * j + k] += a[k] ;
            }
        }
        memcpy(a,b,sizeof(b));
   }
}
int main()
{
    while(scanf("%d",&n)!= EOF)
    {
        memset(a,0,sizeof(a));
        mother_fun();
        printf("%lld\n",a[n]);
    }
    return 0;
}

二、指數型母函數

1.定義

序列a1,a2,...,ana_1,a_2,...,a_n的指數型母函數爲:
EG(an;x)=i=0aixii!\displaystyle EG(a_n;x)=\sum_{i = 0}a_i\frac{x^i}{i!}

2.應用

從上面的學習,我們可以知道:普通型的母函數是一個解決求有限多重集的組合的有效工具,而指數型函數是
指數型母函數主要是解決求有限多重集的排列
設元素a1,a1,,ana_{1},a_{1}, \cdot \cdot \cdot ,a_{n}互不相同,從有限多重集{K1a1,K2a2,Knan}\left\{ K_{1} \cdot a_{1},K_{2} \cdot a_{2}, \cdot \cdot \cdot K_{n} \cdot a_{n} \right\}中選取 rr 個元素,至少存在一個Ki<rK_{i} < r 時,求其排列。

我們比較普通型母函數和指數型母函數,可以看出普通型的母函數的標誌函數爲1,x,x2,...,xn1,x,x^2,...,x^n;而指數型函數的標誌函數爲1,x/1!,x2/2!,x3/3!...,xn/n!1,x/1!,x^2/2!,x^3/3!...,x^n/n!
這樣的映射也有其意義:

xii!\displaystyle\frac{x^i}{i!} 代表的意義是:在某個方案中某個元素出現了 ii 次,而在不同位置中的 ii 次出現是相同的。

另外,在指數型母函數使用的過程中,一般都會用到高等數學裏的exe^x的泰勒展開式:
ex=n=0xnn!=1+x+x22!+x33!...+xnn!+...e^x=\sum_{n=0}\frac{x^n}{n!}=1 + x+\frac{x^2}{2!}+\frac{x^3}{3!}...+\frac{x^n}{n!}+...
(ex+ex)/2=n=0x2n2n!=1+x22!+x44!...+x2n2n!+...(e^x+e^{-x})/2=\sum_{n=0}\frac{x^{2n}}{2n!}=1 +\frac{x^2}{2!}+\frac{x^4}{4!}...+\frac{x^{2n}}{2n!}+...
(exex)/2=n=0x2n+1(2n+1)!=x+x33!+x55!...+x2n+1(2n+1)!+...(e^x-e^{-x})/2=\sum_{n=0}\frac{x^{2n+1}}{(2n+1)!}= x+\frac{x^3}{3!}+\frac{x^5}{5!}...+\frac{x^{2n+1}}{(2n+1)!}+...
下面來舉個例子:

1,2,3,41,2,3,4四個數字組成的五位數中,要求數 11出現兩次或三次,22 最多出現一次,44 出現偶數次。

這很明顯是一個排列數的問題,在我們有了上述普通型母函數的經驗之後,我們可以類似地開始構造我們的指數型母函數:

  1. 11 出現兩次或三次,對應的母函數:x22!+x33!\displaystyle\frac{x^2}{2!}+\frac{x^3}{3!}

  2. 22 最多出現 一次,對應的母函數:1+x1!\displaystyle1+\frac{x}{1!}

  3. 33 出現沒有要求, 1+x1!+x22!+x33!+...+xnn!+...\displaystyle1+ \frac{x}{1!}+ \frac{x^2}{2!} +\frac{x^3}{3!}+...+\frac{x^{n}}{n!}+...

  4. 44 出現偶數次, 1+x22!+x44!+x66!...+x2n2n!+...\displaystyle1+\frac{x^2}{2!} +\frac{x^4}{4!}+\frac{x^6}{6!}...+\frac{x^{2n}}{2n!}+...

那麼本題的母函數:
G(x)=(x22!+x33!)(1+x1!)(1+x1!+x22!+x33!+...+xnn!+...)(1+x22!+x44!+x66!...+x2n2n!+...)\displaystyle G(x) = (\frac{x^2}{2!}+\frac{x^3}{3!})*(1+\frac{x}{1!})*(1+ \frac{x}{1!}+ \frac{x^2}{2!} +\frac{x^3}{3!}+...+\frac{x^{n}}{n!}+...)*(1+\frac{x^2}{2!} +\frac{x^4}{4!}+\frac{x^6}{6!}...+\frac{x^{2n}}{2n!}+...)

因爲,ex=n=0xnn!=1+x+x22!+x33!...+xnn!+...e^x=\sum_{n=0}\frac{x^n}{n!}=1 + x+\frac{x^2}{2!}+\frac{x^3}{3!}...+\frac{x^n}{n!}+...
(ex+ex)/2=n=0x2n2n!=1+x22!+x44!...+x2n2n!+...(e^x+e^{-x})/2=\sum_{n=0}\frac{x^{2n}}{2n!}=1 +\frac{x^2}{2!}+\frac{x^4}{4!}...+\frac{x^{2n}}{2n!}+...

所以
G(x)=(x22!+x33!)(1+x1!)ex(ex+ex)/2G(x)=(\frac{x^2}{2!}+\frac{x^3}{3!})*(1+\frac{x}{1!})*e^x*(e^x+e^{-x})/2
=(x24+x33+x412)(e2x+1)=\displaystyle(\frac{x^2}{4}+\frac{x^3}{3}+\frac{x^4}{12})*(e^{2x}+1)
我們要求的是 55 位數,那麼我們的答案就應該是x55!\displaystyle\frac{x^5}{5!}前面的係數。
我們已經有了x2,x3,x4x^2,x^3,x^4,所以只需要補上x3,x2,x1x^3,x^2,x^1

e2xe^{2x}按泰勒展開後,可以得到係數是:

5!(14233!+12222!+112211!)=1405!*(\frac{1}{4}*\frac{2^3}{3!}+\frac{1}{2}*\frac{2^2}{2!}+\frac{1}{12}*\frac{2^1}{1!})=140

HDU 1521 排列組合
題意

有n種物品,並且知道每種物品的數量。要求從中選出m件物品的排列數。例如有兩種物品A,B,並且數量都是1,從中選2件物品,則排列有"AB","BA"兩種。

思路

根據題意,這是一個多重集的排列數問題,所以考慮使用指數型生成函數,然後就是一頓模板。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#define ll long long
using namespace std;

const int N = 107;
int n,m;
int num[N];
double a[N] = {0},b[N] = {0};
//求階乘
int fac(int n) {
	int res = 1;
	for(int i = 1; i <= n; i++) res *= i;
	return res;
}
//指數型母函數
void mother_fun() {
	for(int i = 0; i <= num[1]; i++) a[i] = 1.0 / fac(i);
	for(int cur = 2; cur <= n; cur++) {
		memset(b,0,sizeof(b));
		for(int i = 0 ; i <= num[cur]; i++) {
			for(int k = 0; k  + i <= m; k++) {
				b[i + k] += a[k] / fac(i) ;
			}
		}
		memcpy(a,b,sizeof(b));
	}
	printf("%.0lf\n",a[m]*fac(m));
}
int main() {
	//freopen("data.in","r",stdin);
	while(scanf("%d%d",&n,&m)!= EOF) {
		memset(a,0,sizeof(a));
		for(int i = 1; i <= n; i++) scanf("%d",num + i);
		mother_fun();
	}
	return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章