母函数在排列组合问题中的应用

Posted by admin on 2012, October 26

在介绍母函数之前,还是先描述一下我们需要用母函数来解决的问题:

  • 假设我们有1元,2元,5元和10元三种硬币,而我们需要20块钱,那么我们可以从这三种硬币中找出多少种总值为20的组合方式呢?比如20个1块钱是一种,而一个10元的加2个5元的硬币又是一种;
  • 饥肠辘辘的猫哥说她要立马吃个十分饱,她面前有三种食物,填充值分别为一成饱的小面包、填充值为三成饱的烤鸡腿和填充值为十成饱的烤乳猪,请问猫哥能找到几种吃法恰好每次都能吃成十成饱

当然,你还可以猛击下面两个连接,看看类似的算法题

http://acm.hdu.edu.cn/showproblem.php?pid=1028

http://acm.hdu.edu.cn/showproblem.php?pid=2082


好了,你可以先考虑一下上面俩问题的解法,我也可以先介绍一下母函数了

母函数也叫生成函数,wiki 中对母函数有详细的描述,而本文所涉及的母函数则限定为普通的母函数。

具体来说,母函数的表现形式为多项式展开式,如:1+x+x2+x3(1+x+x的平方+x的三次方)

每一项的x前面的系数和x的幂级数都保存着我们需要的信息。

比如说给我一堆2块钱的硬币,我们能组合出0,2,4,6,8…..的组合,而系数表示组合的不同方式,幂级数代表总值。

这样无限多个2块钱硬币组合所代表的多项式为 1+x2+x4+x6+x8+……….

可能具体的例子会比较清晰吧:假设给你1元钱,2元钱,3元钱的硬币各一个;其代表的多项式展开如下:

(1+x)(1+X2)(1+x3)

= (1+x+x2+x3)(1+x3)

= 1+x+x2+2x3+x4+x5+x6

注意最后结果: 1+x+x2+2x3+x4+x5+x6 ,比如其中2x3表示为,总额为3的硬币组合有两种。

了解了母函数所表示的信息之后,我们就把原问题转换为了多项式的乘法了。

比如2082 这个问题的第一个Sample Input就是我前面说的这个例子,

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

<div style="font-family:Courier New,Courier,monospace">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</div>

Sample Output

<div style="font-family:Courier New,Courier,monospace">7
379297</div>

而这题的AC代码如下:

1 #include <stdio.h>  

2 #include <stdlib.h>  

3   

4 int
main() {  

5 int
n;  

6 int
input[27];  

7 int
a[52],b[52],ret[52];  

8 int
i,j,k;  

9 int
num;  

10 while(scanf("%d",&n)!=EOF) {  

11 while(n--) {  

12 num=0;  

13 for(i=1; i<=26; i++)  

14 scanf("%d",&input[i]);  

15 for(i=0; i<52; i++)  

16 ret[i] = a[i] = b[i] =
0;  

17 a[0] =
1; //a[]*b[]  

18 for(i=1; i<=26; i++) {
//loop input  

19 if(input[i]>0) {
//the number of this choise is zero  

20 for(j=0; j<=input[i]; j++) {  

21 if(j*i<=50)  

22 b[j*i] = 1;  

23 }  

24 for(j=0;j<=50;j++){  

25 for(k=0;k+j<=50;k++){  

26 if(j+k<=50)  

27 ret[j+k] += a[j]*b[k];  

28 }  

29 }  

30 /*  

31 //前面i种字母的组合结果  

32 printf("##");  

33 for(j=0;j<10;j++)  

34 printf("%d ",ret[j]);  

35 printf("n");  

36 */  

37 //clean b[]  

38 for(j=0; j<=50; j++){  

39 a[j] =
ret[j];  

40 ret[j] = b[j] = 
0;  

41 }  

42 }  

43 }  

44 for(i=1;i<=50;i++){  

45 //printf("%d ",a[i]);  

46 num+=a[i];  

47 }  

48 printf("%dn",num);  

49 }  

50 }  

51 return
0;  

52 }

以上代码在空间和时间上都没有优化,以便于理解

其实,如果使用母函数去理解原题,然后能用代码将一系列的多项式相乘表达出来,这类题目就不在话下了。


PS:

  很多算法书都有一道类似的硬币组合题,大概是在一堆硬币中选择最小的硬币数目来组合出一个较大的额度值,和本文所描述的算法没有直接关系,那道题应该是一道比较典型的动态规划(DP)题


参考资料:

WiKi :http://zh.wikipedia.org/zh/%E6%AF%8D%E5%87%BD%E6%95%B0

什么是生成函数?:http://www.matrix67.com/blog/archives/120