首先:
在数学中,某个序列的母函数(Generating function,又称生成函数)是一种形式幂级数,其每一项的系数可以提供关于这个序列的信息。使用母函数解决问题的方法称为母函数方法。
母函数可分为很多种,包括普通母函数、指数母函数、L级数、贝尔级数和狄利克雷级数。对每个序列都可以写出以上每个类型的一个母函数。构造母函数的目的一般是为了解决某个特定的问题,因此选用何种母函数视乎序列本身的特性和问题的类型。
对于需要用到母函数问题的理解:
1.“把组合问题的加法法则和幂级数的乘幂对应起来”
2.“母函数的思想很简单 — 就是把离散数列和幂级数一 一对应起来,把离散数列间的相互结合关系对应成为幂级数间的运算关系,最后由幂级数形式来确定离散数列的构造. “
解题过程
参考自:TP到大佬
解题时,首先要写出表达式,通常是多项的乘积,每项由多个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];
//初始化a
memset(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
}
有关指数型母函数
可以看一下有关裸题:HDU1521
有关例题:
一、hdu 1085和hdu 1171两题套用了第二个模板,省略了代码中二三层循环里关于last2的条件(其实也可以加上)。
二、hdu 1398套用了第一个模板,因为n2中每一项为无穷大,所以n2数组就省略了。
详见:
hdu 1398:http://blog.csdn.net/xiaofei_it/article/details/17041815
三、hdu 2079、hdu 2082和hdu 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