母函数,又称生成函数,是ACM竞赛中经常使用的一种解题算法,常用来解决组合方面的题目。
母函数通常解决类似如下的问题:
给5张1元,4张2元,3张5元,要得到15元,有多少种组合?
某些时候会规定至少使用3张1元、1张2元、0张5元。
某些时候会规定有无数张1元、2元、5元。
解题时,首先要写出表达式,通常是多项的乘积,每项由多个x^y组成。如(1+x+x^2)(1+x^4+x^8)(x^5+x^10+x^15)。
通用表达式为
(x^(v[0]*n1[0])+x^(v[0]*(n1[0]+1))+x^(v[0]*(n1[0]+2))+...+x^(v[0]*n2[0]))
(x^(v[1]*n1[1])+x^(v[1]*(n1[1]+1))+x^(v[1]*(n1[1]+2))+...+x^(v[1]*n2[1]))
...
(x^(v[K]*n1[K])+x^(v[K]*(n1[K]+1))+x^(v[K]*(n1[K]+2))+...+x^(v[K]*n2[K]))
K对应具体问题中物品的种类数。
v[i]表示该乘积表达式第i个因子的权重,对应于具体问题的每个物品的价值或者权重。
n1[i]表示该乘积表达式第i个因子的起始系数,对应于具体问题中的每个物品的最少个数,即最少要取多少个。
n2[i]表示该乘积表达式第i个因子的终止系数,对应于具体问题中的每个物品的最多个数,即最多要取多少个。
对于表达式(1+x+x^2)(x^8+x^10)(x^5+x^10+x^15+x^20),v[3]={1,2,5},n1[3]={0,4,1},n2[3]={2,5,4}。
解题的关键是要确定v、n1、n2数组的值。
通常n1都为0,但有时候不是这样。
n2有时候是无限大。
之后就实现表达式相乘,从第一个因子开始乘,直到最后一个为止。此处通常使用一个循环,循环变量为i。每次迭代的计算结果放在数组a中。计算结束后,a[i]表示权重i的组合数,对应具体问题的组合数。
循环内部是把每个因子的每个项和a中的每个项相乘,加到一个临时的数组b的对应位(这里有两层循环,加上最外层循环,总共有三层循环),之后就把b赋给a。
这些过程通常直接套用模板即可。
以上都是转自 http://blog.csdn.net/xiaofei_it/article/details/17042651
HDU-1398 Square Coins
题目:有一个序列a[17]=(1,4,9.....,i*i,17*17),对于给出的数字n,求出用给定的序列组合成n的个数。
#include <bits/stdc++.h>
using namespace std;
const int maxn=310;
const int P=300;
int a[maxn],b[maxn],v[20];
void init()
{
for(int i=1;i<=17;i++)
v[i]=i*i;
memset(a,0,sizeof a);
a[0]=1;
for(int i=1;i<=17;i++)
{
memset(b,0,sizeof b);
for(int j=0;j*v[i]<=P;j++)
{
for(int k=0;k+j*v[i]<=P;k++)
b[k+j*v[i]]+=a[k];
}
memcpy(a,b,sizeof a);
}
}
int n;
int main()
{
init();
while(~scanf("%d",&n))
{
if(n==0) break;
printf("%d\n",a[n]);
}
return 0;
}
/*优化后的*/
#include <bits/stdc++.h>
using namespace std;
const int maxn=310;
const int P=300;
int a[maxn],b[maxn],v[20],n1[maxn],n2[maxn];
void init()
{
for(int i=1;i<=17;i++)
v[i]=i*i; //第i个因子的权重,对应于具体问题的每个物品的价值或者权重。可能是读入的值
for(int i=1;i<=17;i++)
{
n1[i]=0; //第i个因子的起始系数,对应于具体问题中的每个物品的最少个数,即最少要取多少个。
n2[i]=P/v[i]; //第i个因子的终止系数,对应于具体问题中的每个物品的最多个数,即最多要取多少个。
}
memset(a,0,sizeof a);
a[0]=1; //答案数组
int last=0; //记录目前最大的指数
for(int i=1;i<=17;i++) //循环因子
{
int last2=min(last+n2[i]*v[i],P);
memset(b,0,sizeof b); //暂时保存答案
for(int j=n1[i];j<=n2[i]&&j*v[i]<=last2;j++) //循环i的系数
{
for(int k=0;k<=last&&k+j*v[i]<=last2;k++) //循环a的系数
b[k+j*v[i]]+=a[k];
}
memcpy(a,b,sizeof(int)*(last2+1)); ///
last=last2; //更新最大指数
}
}
int n;
int main()
{
init();
while(~scanf("%d",&n))
{
if(n==0) break;
printf("%d\n",a[n]);
}
return 0;
}
我还是觉得dp更好。。。
HDU-1028 Ignatius and the Princess III
本题的意思是:整数划分问题是将一个正整数n拆成一组数连加并等于n的形式,且这组数中的最大加数不大于n。如6的整数划分为
6
5 + 1
4 + 2, 4 + 1 + 1
3 + 3, 3 + 2 + 1, 3 + 1 + 1 + 1
2 + 2 + 2, 2 + 2 + 1 + 1, 2 + 1 + 1 + 1 + 1
1 + 1 + 1 + 1 + 1 + 1
共11种。
#include <bits/stdc++.h>
using namespace std;
const int maxn=130;
const int P=120;
int a[maxn],b[maxn],v[130],n1[maxn],n2[maxn];
void init()
{
for(int i=1;i<=P;i++)
v[i]=i;
for(int i=1;i<=P;i++)
{
n1[i]=0;
n2[i]=P/v[i];
}
memset(a,0,sizeof a);
a[0]=1;
int last=0;
for(int i=1;i<=P;i++)
{
int last2=min(last+n2[i]*v[i],P);
memset(b,0,sizeof b);
for(int j=n1[i];j<=n2[i]&&j*v[i]<=last2;j++)
{
for(int k=0;k<=last&&k+j*v[i]<=last2;k++)
b[k+j*v[i]]+=a[k];
}
memcpy(a,b,sizeof(int)*(last2+1));
last=last2;
}
}
int n;
int main()
{
init();
while(~scanf("%d",&n))
{
if(n==0) break;
printf("%d\n",a[n]);
}
return 0;
}
HDU-1085 Holding Bin-Laden Captive!
给你x个一元硬币,y个两元硬币,z个五元硬币,问不能凑出来的第一个面额是多少。
#include <bits/stdc++.h>
using namespace std;
const int maxn=8010;
const int P=8001;
int a[maxn],b[maxn],v[130],n1[maxn],n2[maxn];
void init(int x,int y,int z)
{
v[1]=1;v[2]=2;v[3]=5;
n2[1]=x;n2[2]=y,n2[3]=z;
for(int i=1;i<=3;i++)
n1[i]=0;
memset(a,0,sizeof a);
a[0]=1;
int last=0;
for(int i=1;i<=3;i++)
{
int last2=min(last+n2[i]*v[i],P);
memset(b,0,sizeof b);
for(int j=n1[i];j<=n2[i]&&j*v[i]<=last2;j++)
{
for(int k=0;k<=last&&k+j*v[i]<=last2;k++)
b[k+j*v[i]]+=a[k];
}
memcpy(a,b,sizeof(int)*(last2+1));
last=last2;
}
}
int n,x,y,z;
int main()
{
while(~scanf("%d%d%d",&x,&y,&z))
{
if(x==0&&y==0&&z==0) break;
init(x,y,z);
for(int i=1;i<=8001;i++)
if(!a[i])
{
printf("%d\n",i);
break;
}
}
return 0;
}
再见母函数