在数学中,某个序列的母函数(Generating function,又称生成函数)是一种形式幂级数,其每一项的系数可以提供关于这个序列的信息。使用母函数解决问题的方法称为母函数方法。
母函数讲解
题目:有1克、2克、3克、4克的砝码各一枚,能称出哪几种重量?每种重量各有几种可能方案?
穷举:
①先假设使用一个1g的砝码,在此基础上使用2g的砝码,同上再假设使用一个3g的,再假设使用一个4g的,这种方案称出的重量为10g。
②在1基础上假设没有使用4g的,这种方案称出的是6g。
以此类推……
每个砝码都有两种情况——用还是不用,以此类推总共就有222*2=16种方案(包含一种都没取的0g方案)
我们可以用一个类似离散数学的逻辑式表示砝码组合出来的情况:
||表示析取,也就是编程时的“逻辑或”;
&&表示合取,也就是编程时的“逻辑与”,
析取与合取都是离散数学中的概念,其实就等于编程语言中的逻辑与和逻辑或。
(使用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),x0表示没有使用砝码(重量为0),所以x0 =1(x ^ 0 = 1)。
注意,砝码的质量是以指数形式表示的。
用这种表达式表示4个砝码的组合情况
(x0+x1)* (x0+x2) * (x0+x3)* (x0+x4)
=x0 + x1 + x2 + 2x3 + 2x4 + 2x5 + 2x6 + 2x7 + x8+ x9 + x10
每项前系数代表方案指数情况下的方案数(例如,2x^4表示4g的情况有两个方案)
将系数相加为16,结果与上面穷举的结果是相同的。
接下来我们把开始的题目稍稍变化一下:
求用1g、2g、3g的砝码称出不同重量的方案数 。
和第一种比较的区别是这里每种是无限的。
以2g砝码为例,可以把2个2g砝码看成是4g砝码,3个2g砝码看成是6g砝码,依次类推,把m个n g砝码看成是一个n*m g砝码,以前两个砝码为例,那么多项式相应的就变成
(x0 +x1 + x2 + x3 + x4 + x5 + …… )*(x0 + x2 + x4 + x6 + x8+ x10 + ……)
在实际问题中,会给出一个确定的值,比如说求组成10g的方案有几种。
那么我们就只要在合并后的结果求到最高次的项是x^10即可,后面的项可以忽略不计。
那么要结果中最高为10次方,开始每一种砝码的无限项的表达式也是10次,因为表达式中最低的项有x0 ,所以想在结果中不漏掉出现x10 的项,必须乘之前的项最高的项不能小于x10,而表达式中不可能出现x-1,,所以x11 和任何一项相乘都会大于x10,所以x11 是不需要的,但写上也无妨,不影响结果,但是如果可以有x10(比如3g的砝码组合不出10g的,但是2g的就可以),那就必须写,不然就会漏掉一些方案。
题目是求用1g、2g、3g的砝码称出10g的方案数 。
表达式就是:
(x0 +x1 + x2 + x3 + x4 + x5 + ……x10 )(x0 + x2 + x4 + x6 + x8+ x10 )
*结果就是合并同类项后x10的系数。**
无限砝码的问题需要的运算时间规模就是n3级别。
模板及例题
1028.Ignatius and the Princess IIl
Problem Description
“好吧,似乎第一个问题太容易了。我会让你知道你以后有多愚蠢。” feng5166说。
“第二个问题是,给定正整数N,我们定义这样的方程:
N = a [1] + a [2] + a [3] + ... + a [m];
a [i]> 0,1 <= m <= N;
我的问题是你能为给定的N找到多少个不同的方程式。
例如,假设N是4,我们可以找到:
4 = 4;
4 = 3 + 1;
4 = 2 + 2;
4 = 2 + 1 + 1;
4 = 1 + 1 + 1 + 1;
因此当N为4时结果为5.注意“4 = 3 + 1”和“4 = 1 + 3”是同样的。
现在,你做到了!“
Input
输入包含几个测试用例。每个测试用例包含一个正整数N(1 <= N <= 120),如上所述。输入由文件末尾终止。
Output
对于每个测试用例,您必须输出一个包含整数P的行,表示您找到的不同方程式。
Sample Input
4 10 20
Sample Output
5 42 627
(母函数无限砝码模板)
#include<bits/stdc++.h> #define fio ios::sync_with_stdio(false);cin.tie(0);cout.tie(0); using namespace std; const int maxn = 125; int sup[maxn], temp[maxn]; int main() { fio int target; while (cin >> target) { memset(sup, 0, sizeof(sup)); memset(temp, 0, sizeof(temp)); /*首先对sup初始化,由第一个表达式(1+x+x^2+..x^target)开始, 把质量从0(x^0)到target的所有项系数初始化为1.*/ for (int i = 0; i <= target; i++) { sup[i] = 1; temp[i] = 0; } //外层循环指第i个表达式 for (int i = 2; i <= target; i++) { //中层循环表示前面i個表达式累乘的表达式里第j个变量 for (int j = 0; j <= target; j++) { //后面被乘多项式中的每一项,k+=i:第i个表达式的增量是i for (int k = 0; k+j <= target; k += i) { temp[j+k] += sup[j]; } } //更新。 for (int j = 1; j <= target; j++) { sup[j] = temp[j]; } memset(temp, 0, sizeof(temp)); } //target项数前系数则为方案数 cout << sup[target] << endl; } }
2082.找单词
Problem Description
假设有x1个字母A, x2个字母B,..... x26个字母Z,
同时假设字母A的价值为1,字母B的价值为2,..... 字母Z的价值为26。
那么,对于给定的字母,可以找到多少价值<=50的单词呢?
单词的价值就是组成一个单词的所有字母的价值之和,比如,
单词ACM的价值是1+3+14=18,单词HDU的价值是8+4+21=33。
(组成的单词与排列顺序无关,比如ACM与CMA认为是同一个单词)。
Input
输入首先是一个整数N,代表测试实例的个数。
然后包括N行数据,每行包括26个<=20的整数x1,x2,.....x26.
Output
对于每个测试实例,请输出能找到的总价值<=50的单词数,每个实例的输出占一行。
Sample Input
2 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 9 2 6 2 10 2 2 5 6 1 0 2 7 0 2 2 7 5 10 6 10 2 10 6 1 9
Sample Output
7 379297
TIP:用数组存储每个字母的个数,判断个数为空的字母表达式不进行相乘。
#include<bits/stdc++.h> #define fio ios::sync_with_stdio(false);cin.tie(0);cout.tie(0); using namespace std; const int maxn = 55; int sup[maxn], temp[maxn]; int a[maxn]; /*2082: 样例 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 母函数表达式为: x0 = 1 (x0+1*x^1)(x0+1*x^2)(x0+1*x^3) = (1+x)(1+x^2)(1+x^3) = (1+x+x^2+x^3)(1+x^3) = 1 + x + x^2 + 2*x^3 + x^4 + x^5 + x^6 >=1 && <=50的指数前系数相加为7 */ int main() { fio int n; cin >> n; while (n--) { //存放每个字母有多少个(项) for (int i = 1; i <= 26; i++) cin >> a[i]; //如果a存在<=>a≠0,a个数的指数项前系数赋1(a价值1) for (int i = 0; i <= a[1]; i++) { sup[i] = 1; temp[i] = 0; } //最外层表示正与第i个表达式相乘 for (int i = 2; i <= 26; i++) { if (a[i] != 0) { //中层循环表示sup[]里的每一项 for (int j = 0; j <= 50; j++) { //外层循环表示后面被乘多项式中的每一项 //k+=i:例如b有2(a[i] = 2)个,表达式为1+x^2+x^4; //i*a[i],i为价值,a[i]为个数,相乘为每个字母的最大价值 for (int k = 0; k <= i*a[i] && j+k <= 50; k += i) { temp[j+k] += sup[j];//j+k为相乘后的指数和 } } for (int j = 0; j <= 50; j++) sup[j] = temp[j];//更新 memset(temp, 0, sizeof(temp)); } } int sum = 0; for (int i = 1; i <= 50; i++) { sum += sup[i]; } cout << sum << endl; } }
二维母函数模板
hd2069题目大意:
1,5,10,25,50五种硬币,和一个价值n,问由硬币总数不超过100的方案有多少种
#include<bits/stdc++.h> #define fio ios::sync_with_stdio(false);cin.tie(0);cout.tie(0); using namespace std; const int value[6] = {0, 1, 5, 10, 25, 50}; int c1[300][110], c2[300][110]; int n; int main() { fio while(cin >> n) { memset(c1, 0, sizeof(c1)); memset(c2, 0, sizeof(c2)); for (int i = 0; i <= min(n, 100); i++) c1[i][i] = 1; for (int i = 2; i <= 5; i++) { for (int j = 0; j <= n; j++) { for (int k = 0; k+j <= n; k += value[i]) { for (int l = 0; l <= 100 && l+k/value[i] <= 100; l++) { c2[j+k][l+k/value[i]] += c1[j][l]; } } } for (int j = 0; j <= n; j++) { for (int k = 0; k <= 100; k++) { c1[j][k] = c2[j][k]; c2[j][k] = 0; } } } int ans = 0; for (int j = 0; j <= 100; j++) { ans += c1[n][j]; } cout << ans << endl; } }