母函数代码思路的详细解释

该文章建立在你已经看过母函数的相关数学知识的基础上,如果没有看过,建议看一下 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 那个课件最后一页的练习题粘到这里来:1028170910851171139820692152

本文转自:http://www.cnblogs.com/lijunle/archive/2010/09/04/1817764.html




 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值