在母函数数学中,一系列的母函数(Generating function,也称为生成函数)为形式幂级数,各项系数可提供关于该系列的信息。 用母函数解决问题的方法叫做母函数方法。
母函数可以分为很多类型,例如普通母函数、指数母函数、l级数、贝尔级数和jzdz级数。 每个序列可以为上述每种类型编写一个母函数。 因为构建母函数的目的一般是为了解决某个特定的问题,所以选择什么样的母函数取决于序列自身的特性和问题的类型。
两个重要观点(http://www.wutianqi.com/?) p=596 )1.“将组合问题的加法定律与幂级数的幂对应起来”2 .“母函数的思路很简单——将离散数列与幂级数一一对应起来,将离散数列之间的相互耦合关系与幂级数之间的运算关系对应起来,最后以幂级数形式确定离散数列的结构。 ”“母函数通常解决以下问题:
1克、2克、3克、4克的砝码各有一枚,能说什么样的重量呢? 每个重量有多少种可能的方案?
如果数量没有限制:
求1、2、3张邮票揭示不同数值的方案数量:
大家把这种情况和第一种情况相比有什么不同? 第一个是一个,但在这里一个一个是无限的。 拿例题开刀的第一种类型,有1克、2克、3克、4克的砝码各一块,能称出什么重量? 每个重量有多少种可能的方案?考虑用母函数来解决这个问题:
假设x表示砝码,x的指数表示砝码的重量。 然后,如下所示。
1克的砝码可以用函数1 1*x^1表示,
一个2克的砝码可以用函数1 1*x^2表示,
一个3克的砝码可以用函数1 1*x^3表示,
一个4克的砝码可以用函数1 1*x^4表示,
用1 x^2来说,如上所述,x表示砝码,x的指数表示砝码的重量! 在初始状态下,这里是质量2的砝码。 因此,这里1 1*x^2=1*x^0 1*x^2,即2克的砝码有两种状态,不取或不取为1*x^0,不取则为1*x^2。
“把组合问题的加法法则和幂级数的乘幂对应起来”
接下来讨论上面的1 x^2。 这里x前面的系数有什么意义?
这里的系数表示状态数(方案数) 1 x^2,即1 x^2) 1 x^2,即如上所述不取2克砝码。 此时,有一个状态。 或者拿两克砝码。 这个时候也有一个状态。
如果一些砝码的组合可以称量重量,则可以用上面的一些函数的乘积来表示:
(1 x ) ) 1 x^2) 1 x^3) (1 x^4) ) ) ) ) ) 1 x^4) ) ) ) ) ) 652 )
=(1xx^2x^4) ) 1 x^3 ^4 x^7) )。
=1xx ^ 22 * x ^ 32 * x ^ 42 * x ^ 52 * x ^ 62 * x ^ 7x ^ 8x ^ 9x ^ 10
从上面的函数可以看出,1克到10克,系数是方案数。
例如右端有2^x^5项,也就是说被称为5克的方案有两种。 5=3(2=4) 1; 同样,6=1 2 3=4 2; 10=1 2 3 4。
因此,被称为6克的方案数有2种,被称为10克的方案数有1种。
第二种类型求出用1点、2点、3点邮票贴上不同数值的方案的数量。 以展开后的x^4为例,其系数为4,即4被分割为1、2、3之和的分割方案的数量为4。
即4=1 1 1 1=1 1 2=1 3=2 2
两个概念“整数分割”和“分数分割”:
整数分割是指将整数分解为几个整数之和。 (相当于把n个没有区别的球放入没有n个标记的箱子里。 箱子允许空着,也允许进多个球。
将整数分割为几个整数之和的方法不尽相同,将不同分割法的总数称为分数的分割。
代码模板: # includeiostreamusingnamespacestd; const int _max=10001; //c1是能够保存和组合各质量份铜的数//c2为中间量,在从未保存的情况下为int c1[_max]、c2[_max]; int main () { //int n,I,j,k; int nNum; //int i,j,k; wile(cinnnum ) for ) I=0; i=nNum; //—- { c1[i]=1; c2[i]=0; (for ) I=2; i=nNum; (I )//—-) for ) j=0; j=nNum; j——–for(k=0; k j=nNum; k=I//—-{C2[JK]=C1[J]; (for ) j=0; j=nNum; j )//—-
{ c1[j] = c2[j]; c2[j] = 0; } } cout << c1[nNum] << endl; } return 0;}
注释:
① 、首先对c1初始化,由第一个表达式(1+x+x^2+..x^n)初始化,把质量从0到n的所有砝码都初始化为1.
② 、 i从2到n遍历,这里i就是指第i个表达式,上面给出的第二种母函数关系式里,每一个括号括起来的就是一个表达式。
③、j 从0到n遍历,这里j就是(前面i个表达式累乘的表达式)里第j个变量,(如(1+x)(1+x^2)(1+x^3),j先指示的是1和x的系数,i=2执行完之后变为(1+x+x^2+x^3)(1+x^3),这时候j应该指示的是合并后的第一个括号的四个变量的系数。
④ 、 k表示的是第j个指数,所以k每次增i(因为第i个表达式的增量是i)。
⑤ 、把c2的值赋给c1,而把c2初始化为0,因为c2每次是从一个表达式中开始的。
解题过程
以下参考自https://blog.csdn.net/xiaofei_it/article/details/17042651#comments
解题时,首先要写出表达式,通常是多项的乘积,每项由多个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。
这些过程通常直接套用模板即可。
通用模板 //为计算结果,b为中间结果。int a[MAX],b[MAX];//初始化amemset(a,0,sizeof(a));a[0]=1;for (int i=1;i<=17;i++)//循环每个因子{ memset(b,0,sizeof(b)); for (int j=n1[i];j<=n2[i]&&j*v[i]<=P;j++)//循环每个因子的每一项 for (int k=0;k+j*v[i]<=P;k++)//循环a的每个项 b[k+j*v[i]]+=a[k];//把结果加到对应位 memcpy(a,b,sizeof(b));//b赋值给a}
P是可能的最大指数。拿钞票组合这题来说,如果要求15元有多少组合,那么P就是15;如果问最小的不能拼出的数值,那么P就是所有钱加起来的和。P有时可以直接省略。具体请看本文后面给出的例题。
如果n2是无穷,那么第二层循环条件j<=n2[i]可以去掉。
如何提高效率?
用一个last变量记录目前最大的指数,这样只需要在0..last上进行计算。
这里给出第二个模板:
//初始化a,因为有last,所以这里无需初始化其他位a[0]=1;int last=0;for (int i=0;i<K;i++){ int last2=min(last+n[i]*v[i],P);//计算下一次的last memset(b,0,sizeof(int)*(last2+1));//只清空b[0..last2] for (int j=n1[i];j<=n2[i]&&j*v[i]<=last2;j++)//这里是last2 for (int k=0;k<=last&&k+j*v[i]<=last2;k++)//这里一个是last,一个是last2 b[k+j*v[i]]+=a[k]; memcpy(a,b,sizeof(int)*(last2+1));//b赋值给a,只赋值0..last2 last=last2;//更新last}
另外,至于什么时候用第一个模板,什么时候用第二个模板,就看题目规模。
通常情况下,第一个模板就够用了,上面的那些用第二个模板的题目用第一个模板同样能AC。
但如果数据规模比较大(通常不会有这种情况),就要使用第二个模板了。
以上题目n1均为0。