用母函数的思路解释母函数的代码
該文章建立在你已經看過母函數的相關數學知識的基礎上,如果沒有看過,建議看一下 hdu 論壇的母函數課件,傳送門:http://acm.hdu.edu.cn/forum/read.php?tid=3853
用一個最簡單的例子說明代碼:
硬幣面值有1元、5元、10元、25元、50元,一共5種,對于一個錢數 money,可以有多少中兌現方法?
很容易地構造母函數
G(x) = (x^0 + x^1 + x^2 + …) * (x^0 + x^5 + x^10 + …) * (x^0 + x^10 + x^20 + …)
* (x^0 + x^25 + x^50 + x^75 + …) * (x^0 + x^50 + x^100 + x^150 + …)
數學上的思路就拆開這個多項式因式,看看 x^money 的系數是多少。所以,編程的關鍵是如何實現拆分?
思路是 先把前面兩個因式拆開,得到一個因式,代替第二個因式;然后用第二個因式(求出來的因式)和第三個因式相乘,代替第三個因式;遞歸計算到最后一個因式,得出結果。
由于便于編程的實現,我們在 G(x) 前面加多一個因式 (1*x^0 + 0*x^1 + 0*x^2 + … ),這個因式為1,所以并不改變母函數的值。把已經計算出來的因式的系數存放到一個數組 a 里面,把當前因式與下一個因式乘起來的因式的系數放到另外一個數組 b 里面。兩個因式乘完了,就把 b 數組里面的數據放到 a 數組里面,把 b 數組清空,重復剛才的步驟,直到最后一個因式都乘進去了,才結束。此時,a 數據里面存放的就是拆分開來的多項式的系數。
代碼如下:
#define M 500int a[M]; int b[M];void GenerationFunction() {int cent[5] = {1, 5, 10, 25, 50};memset(a, 0, sizeof(a));memset(b, 0, sizeof(b));a[0] = 1; // 手動添加的一個多項式因式 (1*x^0 + 0*x^1 + 0*x^2 + … )for(int i = 0; i < 5; i ++) // i 指向第幾個多項式因式(從0開始算){for(int j = 0; j < M; j ++){ // j 指向已經算出來的因式的指數,a[j] 存放的是指數為 j 的項的系數for(int k = 0; j+k < M; k += cent[i]){ // k 代表下一個因式的指數,因式的指數是隔 cent[i] 遞增的。// 另外,因式中,指數為 k 的項的系數為 1b[j+k] += a[j]; // 對于 a[j] * x^j * x^k = a[j] * x^(j+k) // 就把 x^(j+k) 的系數 a[j] 加到 存放當前答案的數組 b 里面}}for(int j = 0; j < M; j ++){ // 把數組 b 元素滾動到 數組 a 去,以便遞歸進行乘法運算;另外,清空數組 ba[j] = b[j];b[j] = 0;}} }
顯而易見,a 數組和 b 數據是滾動操作的,可以用一個二維數組,外加一個滾動變量簡單操作:
?
#define M 500int ans[M]; int f = 0;void GenerationFunction() {int cent[5] = {1, 5, 10, 25, 50};memset(ans, 0, sizeof(ans));ans[0][0] = 1; // 初始化第一個多項式因式for(int i = 0; i < 5; i ++){for(int j = 0; j < M; j ++){for(int k = 0; j+k < M; k += cent[i]){ans[1-f][j+k] += ans[f][j];}}memset(ans[f], 0, sizeof(ans[f])); // 清空當前數組,留待下一個循環用f = 1 - f; // 修改滾動變量} }
看了兩段代碼。看過背包的童鞋一般都聯想到背包去了。
對于要求的 money ,理解為背包的大小,每種硬幣的面值理解為貨物的大小,a[i] 就是選擇到當前這個硬幣,里面放 i 大小貨物的方法數。如果題目中硬幣無限個,就是完全背包,如果有些限制條件,就是修改版的完全背包。循環到最后,a[money] 就是選擇完全部硬幣,背包中放 money 大小貨物的方法數,也就是把 money 元兌換成各種硬幣的方法數。
當然,用母函數的思路理解代碼,也是可以的。這樣子的話,基本可以解決 hdu 那個課件里面的題目,除了 hdu 2069 Coin Change。
Coin Change 題目中,有一個要求,就是每一種兌換的方式,里面的硬幣數量不超過 1000 個。用母函數怎么解決這個問題?參考背包的方法——加多一維!代碼如下:
#define M 300int ans[2][M][101]; int f = 0;void GenerationFunction() {int cent[5] = {1, 5, 10, 25, 50};ans[0][0][0] = 1; // 初始化for(int i = 0; i < 5; i ++){for(int j = 0; j < M; j ++){for(int k = 0; j+k < M; k += cent[i]){for(int g = 0; g+k/cent[i] < 101; g ++){// ans[][j][g] 表示放 j 大小的貨物,并且用 g 個物品放,的方法數ans[1-f][j+k][g+k/cent[i]] += ans[f][j][g];}}}memset(ans[f], 0, sizeof(ans[f]));f = 1 - f;} }
看了幾個代碼,基本上,用母函數的思路敲出來的代碼和用背包敲出來代碼是基本一樣的。這意味著母函數的思路其實是背包思路的一種。如果童鞋對于背包熟的話,基本可以很快地消化母函數的代碼,當然,不熟悉背包的話,就像我一樣,用母函數的思路敲代碼。效果是一樣的。
最后,把 hdu 那個課件最后一頁的練習題粘到這里來:1028、1709、1085、1171、1398、2069、2152。
?
本文轉自:http://www.cnblogs.com/lijunle/archive/2010/09/04/1817764.html?
?
轉載于:https://www.cnblogs.com/Chinese-Coder-Clarence/articles/2154581.html
總結
以上是生活随笔為你收集整理的用母函数的思路解释母函数的代码的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 22个小故事
- 下一篇: 9. Approximate Infer