母函数详解和史上最通用最高效的母函数模板【转】

版权所有。所有权利保留。

欢迎转载,转载时请注明出处:

http://blog.csdn.net/xiaofei_it/article/details/17042651



母函数,又称生成函数,是ACM竞赛中经常使用的一种解题算法,常用来解决组合方面的题目。

本文讲解母函数,但不讲解该算法的基础理论。读者随便找一本组合数学教材便可找到相应的内容,或者直接在网上搜索一下。

母函数通常解决类似如下的问题:

给5张1元,4张2元,3张5元,要得到15元,有多少种组合?

某些时候会规定至少使用3张1元、1张2元、0张5元。

某些时候会规定有无数张1元、2元、5元。

……


解题过程

解题时,首先要写出表达式,通常是多项的乘积,每项由多个x^y组成。如(1+x+x^2)(1+x^4+x^8)(x^5+x^10+x^15)。

通用表达式为

(x^(v[0]*n1[0])+x^(v[0]*(n1[0]+1))+x^(v[0]*(n1[0]+2))+...+x^(v[0]*n2[0]))
(x^(v[1]*n1[1])+x^(v[1]*(n1[1]+1))+x^(v[1]*(n1[1]+2))+...+x^(v[1]*n2[1]))
...
(x^(v[K]*n1[K])+x^(v[K]*(n1[K]+1))+x^(v[K]*(n1[K]+2))+...+x^(v[K]*n2[K]))

K对应具体问题中物品的种类数。

v[i]表示该乘积表达式第i个因子的权重,对应于具体问题的每个物品的价值或者权重。

n1[i]表示该乘积表达式第i个因子的起始系数,对应于具体问题中的每个物品的最少个数,即最少要取多少个。

n2[i]表示该乘积表达式第i个因子的终止系数,对应于具体问题中的每个物品的最多个数,即最多要取多少个。

对于表达式(1+x+x^2)(x^8+x^10)(x^5+x^10+x^15+x^20),v[3]={1,2,5},n1[3]={0,4,1},n2[3]={2,5,4}。

解题的关键是要确定v、n1、n2数组的值。

通常n1都为0,但有时候不是这样。

n2有时候是无限大。

之后就实现表达式相乘,从第一个因子开始乘,直到最后一个为止。此处通常使用一个循环,循环变量为i。每次迭代的计算结果放在数组a中。计算结束后,a[i]表示权重i的组合数,对应具体问题的组合数。

循环内部是把每个因子的每个项和a中的每个项相乘,加到一个临时的数组b的对应位(这里有两层循环,加上最外层循环,总共有三层循环),之后就把b赋给a。

这些过程通常直接套用模板即可。

通用模板

下面我直接给出通用模板:


 
 
  1. //为计算结果,b为中间结果。
  2. int a[MAX],b[MAX];
  3. //初始化a
  4. memset(a, 0, sizeof(a));
  5. a[ 0]= 1;
  6. for ( int i= 1;i<= 17;i++) //循环每个因子
  7. {
  8. memset(b, 0, sizeof(b));
  9. for ( int j=n1[i];j<=n2[i]&&j*v[i]<=P;j++) //循环每个因子的每一项
  10. for ( int k= 0;k+j*v[i]<=P;k++) //循环a的每个项
  11. b[k+j*v[i]]+=a[k]; //把结果加到对应位
  12. memcpy(a,b, sizeof(b)); //b赋值给a
  13. }

P是可能的最大指数。拿钞票组合这题来说,如果要求15元有多少组合,那么P就是15;如果问最小的不能拼出的数值,那么P就是所有钱加起来的和。P有时可以直接省略。具体请看本文后面给出的例题。

如果n2是无穷,那么第二层循环条件j<=n2[i]可以去掉。


如何提高效率?

用一个last变量记录目前最大的指数,这样只需要在0..last上进行计算。

这里给出第二个模板:


 
 
  1. //初始化a,因为有last,所以这里无需初始化其他位
  2. a[ 0]= 1;
  3. int last= 0;
  4. for ( int i= 0;i<K;i++)
  5. {
  6. int last2=min(last+n[i]*v[i],P); //计算下一次的last
  7. memset(b, 0, sizeof( int)*(last2+ 1)); //只清空b[0..last2]
  8. for ( int j=n1[i];j<=n2[i]&&j*v[i]<=last2;j++) //这里是last2
  9. for ( int k= 0;k<=last&&k+j*v[i]<=last2;k++) //这里一个是last,一个是last2
  10. b[k+j*v[i]]+=a[k];
  11. memcpy(a,b, sizeof( int)*(last2+ 1)); //b赋值给a,只赋值0..last2
  12. last=last2; //更新last
  13. }

例题

下面看几个例题。

一、hdu 1085hdu 1171两题套用了第二个模板,省略了代码中二三层循环里关于last2的条件(其实也可以加上)。

详见:

hdu 1085:http://blog.csdn.net/xiaofei_it/article/details/17041467

hdu 1171:http://blog.csdn.net/xiaofei_it/article/details/17041709

二、hdu 1398套用了第一个模板,因为n2中每一项为无穷大,所以n2数组就省略了。

详见:

hdu 1398:http://blog.csdn.net/xiaofei_it/article/details/17041815

三、hdu 2079hdu 2082hdu 2110三题直接套用了第二个模板。

详见:

hdu 2079:http://blog.csdn.net/xiaofei_it/article/details/17042045

hdu 2082:http://blog.csdn.net/xiaofei_it/article/details/17042253

hdu 2110:http://blog.csdn.net/xiaofei_it/article/details/17042421

另外,至于什么时候用第一个模板,什么时候用第二个模板,就看题目规模。

通常情况下,第一个模板就够用了,上面的那些用第二个模板的题目用第一个模板同样能AC。

但如果数据规模比较大(通常不会有这种情况),就要使用第二个模板了。

以上题目n1均为0。

四、hdu 2152是一道n1不为0的题目,我在这里分别套用第一个和第二个模板解题。

详见:

hdu 2152:http://blog.csdn.net/xiaofei_it/article/details/17042497


希望大家读了我的这篇文章后能有收获。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值