我们先来说说组合数学中母函数的定义:
简单来说母函数是要来计数的,在计数方面是非常重要的工具。
对于这个函数(或着母函数)来说,函数所关心的是函数的自变量x和G(x),极值,最值,极限等相关的研究,而母函数的研究对象指的是a0,a1,a2......系数。
所以对于序列a0,a1,a2,…构造一函数:称函数G(x)是序列a0,a1,a2,…的母函数。
为何说G(x)与对应的a0,a1,a2......系数会与组合数学有关?可以思考下组合数学在很多方面都涉及到分步与分类涉及到的乘法法则与加法法则,如果把G(x)换种形式:G(x) = (1+x)(1+x2)(1+x3)(1+x4)...,这个多项式种加法和乘法都有应用到,所以呢,也适合去研究组合数学上的很多问题。当然这次只是简单的研究下普通的母函数。
总共讲三类题目。
第一类:
给出一个例题(非常常见的):个数有限
题目:HDU 1085
题意:给出num_1个1,num_2个2,num_5个5,求根据这么多数不能组成的最小的数是多少。
解析:题意非常的简单,就是根据这num_1+num_2+num_5个数能组成些什么数并对它进行标记,当然能组成的最大数max1是num_1+2*num_2+num_5*5。如果从1~max1都能组成的话,那是最好的,都能组成的话,这道题的答案就是max1+1,如果没有被标记到就要被输出,然后结束程序。
我们先建议母函数G(x) = (1+x+x^2+...x^num_1)*(1+x^2+x^4+...x^num_2*2)*(1+x^5+x^10+...x^num_5*5).
我们来分析下这个母函数的构成。这里面涉及到加法与乘法,加法我们可以认为是分类,要么取一个1(x),要么取两个1(x^2),要么不取(1相当于x^0)等。所涉及到的乘法就是取多少个一,取多少个二,取多少个五...等。母函数把组合数学演绎的淋漓尽致,加法法则与乘法法则让母函数有了灵性。
附上代码:
#include<cstdio> #include<cstring> #include<iostream> #include<sstream> #include<algorithm> #include<vector> #include<bitset> #include<set> #include<queue> #include<stack> #include<map> #include<cstdlib> #define maxn 1*1000+2*1000+5*1000+15 int a, b, c; int d1[maxn], d2[maxn], d3[maxn]; using namespace std; int main() { while(scanf("%d%d%d", &a, &b, &c) != EOF) { if(a == 0 && b == 0 && c == 0) break; memset(d1, 0, sizeof(d1)); memset(d2, 0, sizeof(d2)); memset(d3, 0, sizeof(d3)); ///系数的相加,母函数 for(int i = 0; i <= a; ++i) d1[i] = 1; ///一的情况 for(int i = 0; i <= a; ++i) for(int j = 0; j <= 2 * b; j += 2) d2[j + i] += d1[i];///一加二的情况 for(int i = 0; i <= a + 2 * b; ++i) for(int j = 0; j <= 5 * c; j += 5) d3[j + i] += d2[i];///一加二加五的情况 int flag = 1; for(int i = 0;i <= a+2*b+5*c;++i) { if(d3[i]==0) { printf("%d\n",i); flag = 0; break; } } if(flag) printf("%d\n",a+2*b+5*c+1); } return 0; }
照样给出一个例题:(个数无限)
题目: HDU1398
题意:给出一个数字w,求根据1,4,9,16...n^2这些数个数不限,组合成w这个数有多少种组合。
解析:我们直接构造母函数吧。G(x) = (1+x+x^2+x^3+...)(1+x^4+x^8+x^12...)(1+x^9+x^18+x^27...)可能有人就会问这些数的个数并不确定如何去进行计算呢?但是要注意到这些数能构成w就可以了,超过了w就没有任何的意义。所以我们可以把临界条件设置成w就可以避免不必要的计算。所以这道题也就能够做出来。
附上代码:
#include<cstdio> #include<cstring> #include<iostream> #include<sstream> #include<algorithm> #include<vector> #include<bitset> #include<set> #include<queue> #include<stack> #include<map> #include<cstdio> #include<cstdlib> #define maxn 305 using namespace std; int n; int c1[maxn], c2[maxn]; int main() { while(scanf("%d", &n) != EOF && n) { for(int i = 0; i <= n; ++i) c1[i] = 1;///系数为1 memset(c2, 0, sizeof(c2)); int N; ///能用到最大的N^2 for(int i = 1;; i++) { if(n < i * i) { N = i - 1; break; } } ///直接设置了边界 for(int i = 2; i <= N; ++i) { for(int j = 0; j <= n; ++j)///原有的多少次方 for(int k = 0; k <= n; k += i * i)///需要增加的多少次方 ///幂乘 c2[j + k] += c1[j]; ///c1永远保存的是第一个括号内各项的下标,c2只是在前两个括号相乘时,存放新的各项下标,算完后再更新到c1中 for(int v = 0; v <= n; ++v) c1[v] = c2[v], c2[v] = 0; } printf("%d\n", c1[n]); } return 0; }
借用别人博客的一些话:
这里给出两句话:
"把组合问题的加法法则和幂级数的t的乘幂的相加对应起来"
"母函数的思想很简单—就是把离散数列和幂级数一一对应起来,把离散数列间的相互结合关系对应成为幂级数间的运算关系,最后由幂级数形式来确定离散数列的构造. "
给出几道题目去锻炼下:HDU 1028(整数拆分) 1398 1085 1171 1709(天平) 2069 2152