该文章建立在你已经看过母函数的相关数学知识的基础上,如果没有看过,建议看一下 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 500
int 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 的项的系数为 1
b[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 去,以便递归进行乘法运算;另外,清空数组 b
a[j] = b[j];
b[j] = 0;
}
}
}
显而易见,a 数组和 b 数据是滚动操作的,可以用一个二维数组,外加一个滚动变量简单操作:
#define M 500
int 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 300
int 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