在数学中,某个序列 (an)n∈N 的母函数(又称生成函数,英语:Generating function)是一种形式幂级数,其每一项的系数可以提供关于这个序列的信息。使用母函数解决问题的方法称为母函数方法。
母函数可分为很多种,包括普通母函数、指数母函数、L级数、贝尔级数和狄利克雷级数。对每个序列都可以写出以上每个类型的一个母函数。构造母函数的目的一般是为了解决某个特定的问题,因此选用何种母函数视乎序列本身的特性和问题的类型。
母函数,又称生成函数,是ACM竞赛中经常使用的一种解题算法,常用来解决组合方面的题目。
生成函数的定义:g(x)=a0+a1x+a2x2+a3x3+… 称g(x) 是序列(a0,a1,a2,a3…)的生成函数;
下面咱们以题目解答的形式,来深入的探讨普通母函数的原理
我们用母函数来解决这个问题
1个1克砝码可以看成1+x^1,1表示不取,x^1表示取一个,以下同理
1个2克砝码可以看成1+x^2
1个3克砝码可以看成1+x^3
1个4克砝码可以看成1+x^4
那么生成函数就是
g(x)=(1+x1)(1+x2)(1+x3)(1+x4)
=1+x+x2+2x3+2x4+2x5+2x6+2x7+x8+x9+x10
这个函数中可以看出重量为3克的方案有两种,重量为7的方案有两种,重量为10的有1种。
不难发现指数表示重量,系数表示方案数。
那么生成函数就是
以展开后的x^4为例,其系数为4,即4拆分成1、2、3之和的拆分方案数为4;
即 :4=1+1+1+1=1+1+2=1+3=2+2
//编程解决
/*
author: 熊谦智
题目描述:母函数模板
涉及算法:这种母函数是表示从每一类中每次能拿一个价值是a[i]的元素 可以拿无限多个
*/
#include
#include
using namespace std;
const int N = 105, M = 1e6+5;
int dp[M], temp[M];
int main() {
int n, target; // n 表示数的种类 target 表示最终要求的值的方案数
while (cin >> n >> target) {
memset(dp,0,sizeof(dp));
memset(temp,0,sizeof(temp));
// 输入每个种类所表示的值
for (int i = 0; i < n; i++) {
cin >> a[i];
}
sort(a,a+n);
// 先用第一种 (最小的一种)初始化 dp数组 -- 对应母函数的第一个括号
for (int i = 0; i < target; i+=a[0]) { // ---- ①
dp[i] = 1;
}
for (int i = 1; i < n; i++) { // ----- ②
for (int j = 0; j <= target; j++) { // ----- ③
for (int k = 0; k+j <= target; k+=a[i]) { // ---- ④
temp[k+j] += dp[j];
}
}
for (int j = 0; j <= target; j++) { // ---- ⑤
dp[j] = temp[j];
temp[j] = 0;
}
}
cout << dp[target] << endl;
}
}
① 、首先对dp初始化,由第一个表达式(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)。
⑤ 、把temp的值赋给dp,而把temp初始化为0,因为temp每次是从一个表达式中开始的。
例题 :
- 可以搜蓝桥杯 包子凑数 这题既可以用完全背包 也可以用母函数来解答
- nyoj -90-数的划分 http://nyoj.top/problem/90
- hdu 1085(硬币问题)
- nyoj-588-Money
// 之所以可以用下面的代码实现是因为题目中给出的就是 a[i] = i
// 而当a[i] != i 就不能用下面的代码实现
#include
using namespace std;
const int _max = 10001;
// c1是保存各项质量砝码可以组合的数目
// c2是中间量,保存每一次的情况
int c1[_max], c2[_max];
int main()
{ //int n,i,j,k;
int nNum; //
int i, j, k;
while(cin >> nNum)
{
for(i=0; i<=nNum; ++i) // ---- ①
{
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[j+k] += c1[j];
}
for(j=0; j<=nNum; ++j) // ---- ⑤
{
c1[j] = c2[j];
c2[j] = 0;
}
}
cout << c1[nNum] << endl;
}
return 0;
}
设生成函数
第几个大括号表示是第几个袋子 x 的指数表示从中取多少个球
最后指数为r的那一项的系数就是方案数。
/*
author: 熊谦智
题目描述:
涉及算法:这种母函数是表示从每一类中可以抽取 1-a[i] 个
*/
#include
#include
using namespace std;
const int N = 105, M = 1e6+5;
int dp[M], temp[M];
int main() {
int n, target;
while (cin >> n >> target) {
memset(dp,0,sizeof(dp));
memset(temp,0,sizeof(temp));
// 输入每个种类 的个数
for (int i = 0; i < n; i++) {
cin >> a[i];
}
sort(a,a+n);
// 先用第一种 (最小的一种)初始化 dp数组 -- 对应母函数的第一个括号
for (int i = 0; i <= a[0] && i <= target; i++) { // ---- ①
dp[i] = 1;
}
for (int i = 1; i < n; i++) {
for (int j = 0; j <= target; j++) {
for (int k = 0; k+j <= target && k <= a[i]; k++) {
temp[k+j] += dp[j];
}
}
for (int j = 0; j <= target; j++) {
dp[j] = temp[j];
temp[j] = 0;
}
}
cout << dp[target] << endl;
}
}
/*
// 表示有3个网袋 从中选取10个求 共有多少总不同的方法
3 10
4 2 9
*/
总结一下,生成函数大多用来解决有限或无限物体的组合方案。
参考链接:
https://blog.csdn.net/vsooda/article/details/7975485
https://blog.csdn.net/qq_41357771/article/details/83449481
https://blog.csdn.net/yu121380/article/details/79914529
https://www.cnblogs.com/13224ACMer/p/4671551.html
https://blog.csdn.net/shouwang_tomorrow/article/details/49157527