在数学中,某个序列的母函数(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; }