母函数原理和应用

【定义】

生成函数即母函数,是组合数学中尤其是计数方面的一个重要理论和工具。生成函数普通母函数、指数母函数、L级数、贝尔级数和狄利克雷级数,ACM应用其中普通型用的比较多,其次就是指数母函数,下面我们对这两个讲解。形式上说,普通型生成函数用于解决多重集的组合问题,而指数型母函数用于解决多重集的排列问题。母函数还可以解决递归数列的通项问题(例如使用母函数解决斐波那契数列的通项公式),这是后话。

具体理解:https://blog.csdn.net/matrix67/article/details/4780458

我们下面主要介绍它的简单理解和ACM中的应用。

参考:https://blog.csdn.net/xiaofei_it/article/details/17042651#comments

普通型母函数

公式

将任意一个序列a0,a1,a2,a3,⋯ an以一个函数的方式联系起来

G(x)=a0x0+a1x1+a2x2……+anxn

则称G(x)称为序列的生成函数

 

普通型母函数可以解决多重集的组合问题

应用

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

51元,42元,35元,要得到15元,有多少种组合?

某些时候会规定至少使用31元、12元、05元。

某些时候会规定有无数张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}

 

解题的关键是要确定vn1n2数组的值。

 

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

 

n2有时候是无限大。

 

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

 

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

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

通用模板

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

//a为计算结果,储存系数,即方案数,b为中间结果。
int a[MAX],b[MAX];
//初始化a
memset(a,0,sizeof(a));
a[0]=1;
for (int i=1;i<=n;i++)//n代表多项式数目,循环每个式子
 //k就是第i个表达式前面那个式子里的第k个变量,如:(1+x)(1+x^2)(1+x^3),
 //a[k]先指示的是1和x的系数,i=2执行完之后变为(1+x+x^2+x^3)(1+x^3),
 //这时候a[k]应该指示的是合并后的第一个括号的四个变量的系数	
 {
memset(b,0,sizeof(b));
for (int j=n1[i];j<=n2[i]&&j*v[i]<=P;j++)//j代表下一个加入乘法的多项式,循环每个因子的每一项
  for (int k=0;k+j*v[i]<=P;k++)        //循环a的每个项,k代表前i个已经乘完的多项式的结果
	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=1;i<=n;i++)
{
	int last2=min(last+n2[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
}

指数型母函数

参照:https://blog.csdn.net/ftx456789/article/details/79084138

指数型母函数可以解决多重集的排列问题 


我们可以利用这个性质来推出一个排列问题的通项 
例:红、白、蓝涂色1xnn的方格要求偶数个为白色,问有多少种方案? 
解: 
列出生成函数

HDU1521 (代码模板)

猜你喜欢

转载自blog.csdn.net/sdz20172133/article/details/81412376
今日推荐