今天看了下母函数,纠结了好长时间,终于在和队友的讨论下自我感觉弄懂了。。。
母函数多用于处理排列组合问题,我们还是拿常见的举例子吧:
假如现在有面值为 2 3 5 的三种硬币各一枚,问能组成多少种不同的面值?每种面值的组合方式有多少种?
首先,我们用X表示硬币,X的指数表示硬币能表示的面值。那么,如果用函数表示每个硬币可以组成的面值,那么
面值为2的硬币可以表示的函数为 x^0+x^2;
面值为3的硬币可以表示的函数为 x^0+x^3;
面值为5的硬币可以表示的函数为 x^0+x^5;
即如果该硬币参与组成面值,那它能表示的面值为2,3,5,如果不参与,就相当于组成的面值为0;
把上面的多项式乘起来,我们会发现一点小规律:
(x^0+x^2)*(x^0+x^3)=x^0+x^2+x^3+x^5;
(x^0+x^2+x^3+x^5)*(x^0+x^5)=x^0+x^2+x^3+2*(x^5)+x^7+x^8+x^10;
我们发现:
2 3 5能组成面值为0的种数为1,即都不选
2 3 5能组成面值为2的种数为1,即选2
2 3 5能组成面值为3的种数为1,即选3
2 3 5能组成面值为5的种数为2,即选5或(2,3)
2 3 5能组成面值为7的种数为1,即选(2,5)
2 3 5能组成面值为8的种数为1,即选(3,5)
2 3 5能组成面值为10的种数为1,即选(2,3,5)
二项式的项数就是能组成的面值的种数,二项式 每项前面的系数就是 组成该项二项式指数的种数(二项式的指数表示能组成的面值)
上面介绍的是给定你每个面值的数量为1,现在假如三种面值的数量依次为3 7 4,那能每项能构成的函数是什么呢?
面值为2的硬币可以表示的函数为 x^0+x^2+x^4+x^6;
面值为3的硬币可以表示的函数为 x^0+x^3+x^6+x^9+x^12+x^15+x^18+x^21;
面值为5的硬币可以表示的函数为 x^0+x^5+x^10+x^15+x^20;
我们又会从上式中发现这样一个规律:
对于第i个二项式的第k项的指数,它是这样构成的:即由第i个数的权值(即硬币面值)*k(k为当前面值的硬币的个数,k的范围为0-k);
上面介绍的是每种硬币的数量为定值,如果每种硬币的数量为无穷多个,那该如何表示?
面值为2的硬币可以表示的函数为 x^0+x^2+x^4+x^6+...;
面值为3的硬币可以表示的函数为 x^0+x^3+x^6+x^9+...;
面值为5的硬币可以表示的函数为 x^0+x^5+x^10+x^15+...;
通过上面的构造函数,也可以发现大概也只有这三种问题:
1,给你多种东西,每种只有一个,求构造一个X的组合数
2,给你多种东西,每种有mi个,求构造一个X的组合数
3,给你多种东西,每种无穷多,求构造一个X的组合数
通过上面的三个问题,我们又会发现,只要构造出来的二项式的指数小于等于X,那么它就是一个合理的构造,现在的问题就转化为用函数构造二项式;
说完上面的普通母函数,也说一下指数型母函数:
其实这个不算是一个问题,因为有模板
模板之一如下:
memset(c1,0,sizeof(c1));
c1[0]=1; //初始化
for(int i=1; i<=m; i++) //循环每个因子,一般来说种类数(因子数)都会给出来
{
memset(c2,0,sizeof(c2));
for(int j=0; j*v[i]<=n; j++)//每个因子的每一项---->1
{
for(int k=0; k+j*v[i]<=n; k++)//循环c1数组的每一项----->2
c2[k+j*v[i]] += c1[k];
}
memcpy(c1,c2,sizeof(c2));
}
printf("%d\n",c1[n]);
这里详细说一下1,2位置的不同处理方法:
对于位置1,循环的是每个因子的每一项,一般情况下每个因子的权值是已知的,需要组合出的数也是已知的,如果每个因子的个数num【i】都知道,则限制条件就是
j<=num[i]&&j*v[i]<=n;如果每个因子的个数无穷多,则j<=num[i]可省略;
对于位置2,循环的事c1数组里的每一项,故k从0开始循环,并且要保证k+j*v【i】小于等于要组合出的数n
对于模板之二,其实就是和模板一差不多,不过模板二对每次循环的边界加了控制,避免的不必要的循环;
模板之二如下:
//假设v[i]为第i个物品的价值,num[i]为第i个物品的数量
memset(c1,0,sizeof(c1));
c1[0]=1;
int max=0,max1;//max为当前c1数组里最大的指数,即最大能组合出的数
for(int i=1; i<=n; i++)//循环每个因子
{
max1=max+v[i]*num[i];//每次循环的界限控制的恰到好处
for(int j=0; j<=num[i]; j++)//每个因子的每一项,共有num[i]+1项
{
for(int k=0; k<=max; k++)//循环c1数组的每一项
c2[k+j*v[i]] += c1[k];
}
max=max1;
for(int i=0; i<=max1; i++)
c1[i]=c2[i],c2[i]=0;
}
对于模板三,就是平常不会经常见到的指数型母函数模板,用处不很大,因为要涉及到阶乘运算,所以数据范围大的话就会溢出;
模板之三如下:
#include <stdio.h>
#include <string.h>
const int MAX=30;
double c1[MAX],c2[MAX];
int f[11];
void fib(int n)//计算阶乘
{
f[0]=1;
for(int i=1; i<=n; i++)
f[i]=f[i-1]*i;
}
int main()
{
fib(10);
int N,M,num[110];
while(~scanf("%d%d",&N,&M))
{
for(int i=1; i<=N; i++)
scanf("%d",&num[i]);
memset(c1,0,sizeof(c1));
c1[0]=1;
for(int i=1; i<=N; i++)//循环每个因子
{
memset(c2,0,sizeof(c2));
for(int j=0; j<=num[i]&&j<=M; j++) //每个因子的每一项
for(int k=0; k+j<=M; k++) //循环c1数组的每一项
c2[k+j] += (c1[k]/f[j]);
memcpy(c1,c2,sizeof(c2));
}
printf("%.lf\n",c1[M]*f[M]);
}
return 0;
}
其实这是hdu 1521题,感觉细节很多,就直接全部写上去了,首先 c1,c2为double型;其次,int型最多能存下12!,13!就超int了,long long(__int64)最多能存下20!;
然后就是循环,和普通母函数一样,就计算时略微不同,这个对照上面的指数母函数多项式理解;最后就是输出用double型.lf,应该就这么多了。。。