在數學中,某個序列的母函數(Generating function,又稱生成函數)是一種形式冪級數,其每一項的系數可以提供關於這個序列的信息。使用母函數解決問題的方法稱為母函數方法。
母函數———把組合問題的加法法則和冪級數的的乘冪的相加對應起來
我們從經典的砝碼的例子講起
題目:有1g 2g 3g 4g的砝碼各一枚,能稱出多少種重量?每種重量的可能組合砝碼是什么
窮舉的話,很容易得出結果,單數時間復雜的度為n的四次方,較大,不能采取
所以,我么可以采用一個類似離散數學的邏輯式子表示前兩種砝碼組合產生的情況
這里 ||代表或 &&代表與
(使用1g||不使用1g)&&(使用2g||不適用2g)
=使用1g&&使用2g||不使用1g&&使用2g||使用1g&&不使用2g||不使用1g&&不使用2g
思考:大家可以發現這個表達式和一種表達式很像,沒錯,如果把“||”看成加法,“&&”看成乘法,和多項式的乘法一模一樣。那么我們直覺的想到,有沒有可能用多項式乘法來表示組合的情況呢?我們再來看題目,題目需要的是幾種砝碼組合后的重量,是一個加法關系,但是在上式中“&&”是一種類似於乘法的運算關系,這怎么辦呢?有沒有什么這樣一種運算關系,以乘法的形式運算,但是結果表現出類似於加法的關系呢?正好有一個,那就是冪運算。Xm 乘上Xn結果是Xm+n,他完美的符合了我們的要求。那么以次數表示砝碼的質量, 就可以以多項式的形式表示砝碼組合的所有方案。
還是以前倆個砝碼為例說明。表示1g砝碼的兩種多項式就是(x^0+x^1),表示2g砝碼的兩種多項式就是(x^0+x^2),x的0次方表示沒有使用該砝碼,當然x的0次方等於1,所以寫成1也是對的。注意,砝碼的重量是用次數表示的,而不是用下標表示的
(x^0+x^1)*(x^0+x^2)
=x^0*x^0+x^1*x^1+x^0*x^1+x^1*x^2
=x^0+x^1+x^2+x^3
結果很顯然,有四個方案;0g 1g 2g 3g
再試試四個砝碼加一起的結果
一個1g 2g 3g 4g
(x^0+x^1)* (x^0+x^2) * (x^0+x^3)* (x^0+x^4)
=x^0 + x^1 + x^2 + 2x^3 + 2x^4 + 2x^5 + 2x^6 + 2x^7 + x^8+ x^9 + x^10
結果就是0g 1g 2g 2個3g 2個4g 2個5g 2個6g 2個7g 一個8g 一個9g 一個10g
至此也就得出了答案。這就是普通母函數,現在你可以回頭去看看我前面說的那兩句話——把組合問題的加法法則和冪級數的的乘冪的相加對應起來,把離散數列間的相互結合關系對應成為冪級數間的運算關系,最后由冪級數形式來確定離散數列的構造。
接下來我們再將題改變一下 ,不限制砝碼的數量,有無限個1g 2g 3g的砝碼,問能組成不同重量級的方案數
怎樣在母函數里表現出無限這種性質呢?很簡單,我們以2g砝碼為例,因為我們有無限個2g砝碼,所以我們可以把2個2g砝碼看成是4g砝碼,3個2g砝碼看成是6g砝碼,依次類推,把m個n g砝碼看成是一個n*m g砝碼,還是先以前兩個砝碼為例,那么多項式相應的就變成
(x^0 +x^1 + x^2 + x^3 + x^4 + x^5 + …… )*(x^0 + x^2 + x^4 + x^6 + x^8+ x^10 + ……)
結果自然也是無限的,但是題目一般都會給出一定的限制條件
結果自然也是無限的,但是這種問題在實際問題中,一定會給出一個確定的值,比如說求組成10g的方案有幾種。那么我們就只要在合並后的結果求到最高次的項是x10即可,后面的項可以忽略不計。那么要結果中最高為10次方,開始每一種砝碼的無限項的表達式該寫到幾次呢?也是10次,因為表達式中最低的項有x0 ,所以想在結果中不漏掉出現x10 的項,必須乘之前的項最高的項不能小於x10,而表達式中不可能出現x-1,,所以x11 和任何一項相乘都會大於x10,所以x11 是不需要的,但寫上也無妨,不影響結果,但是如果可以有x10(比如3g的砝碼組合不出10g的,但是2g的就可以),那就必須寫,不然就會漏掉一些方案。
那么如果題目是求用1g、2g、3g的砝碼稱出10g的方案數 。
表達式就是:
(x^0 +x^1 + x^2 + x^3 + x^4 + x^5 + ……x^10 )*(x^0 + x^2 + x^4 + x^6 + x^8+ x^10 )
結果就是合並同類項后x10的系數。
多項式乘法顯然是一種運算時間規模在n2級別的運算,但是如果循環生成無限的砝碼,也就是上面用小砝碼組合出每一種可能的大砝碼,那么生成多項式就又需要n的規模,在此基礎上進行多項式乘法,最后無限砝碼的問題需要的運算時間規模就是n3級別。
這樣母函數就把這類組合問題從nn級別轉化成了n3級別。這便是母函數的奇妙與威力。
知道了母函數的原理,用程序來實現也就不是什么困難的事了,其實就是做多項式乘法的程序
例題1:
這個模板是所有小於t的的數的組合數的總數
4 = 3 + 1;
4 = 2 + 2;
4 = 2 + 1 + 1;
4 = 1 + 1 + 1 + 1;
#include <iostream> using namespace std; const int maxn = 10010; int sup[maxn],temp[maxn]; int main() { int t; int i,j,k; while(cin>>t) { for(i=0;i<t;i++) { sup[i] = 1 ; temp[i] = 0; } for(i=2;i<=t;i++) { for(j=0;j<=t;j++) { for(k=0;k+j<=t;k+=i) { temp[j+k] += sup[j]; } } for(j=0;j<=t;++j) { sup[j] = temp[j]; temp[j] = 0; } } cout<<sup[t]<<endl; } return 0; }
例題2
杭電ACM hdu 1085 Holding Bin-Laden Captive!
大意是 第一行輸入三個數字a b c 代表你擁有的1元 2元 5元的硬幣數量
請輸出您不能用給定的硬幣支付的最小值。
這里a數組就是代表的
#include <iostream> #include <bits/stdc++.h> using namespace std; const int maxn = 10010; int n[3],a[9999],b[9999],i,j,k,last,maxx; int v[3]={1,2,5}; int main() { while((cin>>n[0]>>n[1]>>n[2])&&(n[0]!=0||n[1]!=0||n[2]!=0)) { a[0] = 1; last = 0; for(i=0;i<=2;i++) { maxx = last + n[i]*v[i]; memset(b,0,sizeof(int)*(maxx+1)); for(j=0;j<=n[i];j++) { for(k=0;k<=last;k++) { b[k+j*v[i]]+=a[k]; } } memcpy(a,b,sizeof(int)*(maxx+1)); last = maxx ; }
for(i=0;i<=last;i++) { if(a[i] == 0) break; } cout<<i<<endl; } return 0; }
例題3
無限的砝碼(板子)
#include <iostream> using namespace std; // Author: bjr // const int max = 1000; // sup是保存多項式的數組,sup[n]中的值代表xn的系數 // temp是臨時多項式,保存相乘的臨時中間情況 int sup[max], temp[max]; /* 程序始終只計算兩個多項式之間的乘積,多個多項式的情況 先計算前兩個的乘積,將結果作為第一個多項式,再與第三個相乘 依次類推,sup始終存放當前運算后的結果然后作為被乘多項式, */ int main() { int target; // 目標重量, 比如上面的例子里就是10,要<max的值 int i, j, k; while(cin >> target) { for(i=0; i<=target; ++i) { sup[i] = 1; //初始化第一個多項式,也就是用1g砝碼的多項式, //注意如果題目沒給1g的砝碼那么就不能++i,而要加上砝碼的質量 temp[i] = 0; //將臨時區清空,無論第一個多項式質量是幾都要全部置零 } for(i=2; i<=target; ++i) // 生成后續的第i個多項式,此題中是2g,i從2開始。 //如果砝碼的值不是規律增長,i可能需要取決於輸入 { for(j=0; j<=target; ++j) // 遍歷當前結果多項式的每一項(當前結果的第j項)與第i個多項式相乘, for(k=0; k+j<=target; k+=i) // 遍歷第i個多項式的每一項,此處構造用小砝碼組成大砝碼的多項式 { temp[j+k] += sup[j]; //冪運算,注意理解 } for(j=0; j<=target; ++j) // 將臨時的結果覆蓋當前結果,同時把臨時結果置零,為下次做准備 { sup[j] = temp[j]; temp[j] = 0; } } cout << sup[target] << endl; //輸出結果 } return 0; }