在数学中,某个序列的母函数(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;
  }
}