目录
生成函数即母函数,是组合数学中尤其是计数方面一个重要理论和工具。
定义
对于任意数列用一个函数
联系起来,我们称G(x)为该数列的生成函数
-----------------------------------------------------------
作用
我们可以用生成函数来求出斐波那契数列的通项公式,解法如下:
相当于G(x)的序列全部左移一下再去掉多余的1即
立即推 ,其实还可以继续化简,我就不化了
---------------------------------------------------------------
从一道简单的题目开始练起
我们有1克,2克,3克,4克的砝码各一个,还有一个天平,假设天平左边不能放砝码的话(左物右码),能称出哪些重量的物品,并求出对应方案数
解:我们用来表示方案,其中
表示方案数,
表示重量
则只用1克可以称出 ,1表示不放
只用2克可以称出 ,同理3的
,4的
,最后因为相互独立,可以乘起来
则
例如重量为6的有1,2,3和2,4两种方案
--------------------------------------------------------------
这次我们有质量为a1,a2,a3,a4...ak克的砝码各一个,还有一个天平,左右两边都能放但是物品只能放左边,问重量为n克的物品有几种称法
解我们用 来表示第i个砝码,其中
表示质量为ai的砝码放在天平左边,1表示不放,
表示放在右边
因为相互独立乘起来就可以的到G(x),最后输出的系数
----------------------------------------------------------
模板
下面给出模板(系数都是1的那种)
#include<iostream>
#include<cstring>//memset和memcpy都在这里
int main(){
const int manN=100001;
int n;
//a是x的系数,b是中间变量,v是每个物品的价值,n1和n2是物品的最少数量和最多数量
int a[maxN],b[maxN],v[n],n1[n],n2[n];
memset(a,0,sizeof(n);
a[0]=1;
for(int i=0;i<n;++i){//从第一个物品循环到最后一个物品
memset(b,0,sizeof(b));//清空中间变量数组
for(int j=0;j<maxN;++j)//第i-1个物品乘完会得到一个结果,让第i个物品的每一项去乘之前的答案
for(int k=n1[i];k<=n2[i]&&j+k*v[i]<maxN;++k)//从n1到n2,j是之前i-1个物品乘完之后x^j,k*v[i]是k个价值为v[i]的物品
b[j+k*v[i]]+=a[j];//对应的x^(j+k*v[i])的系数就是x^j的系数加上去
memcpy(a,b,sizeof(b));//把b数组复制给a数组
}
}
如果数据量比较大,可以考虑用第二个模板
#include<iostream>
#include<cstring>//memset和memcpy都在这里
int main(){
const int manN=100001;
int n;
//a是x的系数,b是中间变量,v是每个物品的价值,n1和n2是物品的最少数量和最多数量
int a[maxN]={1},b[maxN],v[n],n1[n],n2[n],last=0,last2;//last是上一次乘完之后最大的幂,last2是这次最大能到达的幂
for(int i=0;i<n;++i){
last2=min(last+v[i]*n2[i],maxN);//last2最大能到达last+第i个物品的价值*第i个物品最多有多少个,但是也不能超过最大的maxN
memset(b,0,sizeof(int)*(last2+1);//只清空b[0....last2],因为只会用到这么多
for(int j=0;j<=last;++j)//只乘a[0....last]的
for(int k=n1[i];k<=n2[i]&&j+k*v[i]<=last2;++j)b[j+k*v[i]]+=a[j];
memcpy(a,b,sizeof(int)*(last2+1);
last=last2;//把last2给last
}
}
---------------------------
题目
来点题目试一下
hdu1085
题目大意是说,给你1,2,5这几个硬币,每一个有a,b,c个,问你最小的不能达到的价值是多少
模板题
#include<iostream>
#include<cstring>
int main(){
const int maxN=9000;
//n数组是每个硬币有多少个,v是硬币的价值
int a[maxN],b[maxN],n[3],v[3]={1,2,5},last,last2;
while(scanf("%d%d%d",&n[0],&n[1],&n[2])&&n[0]+n[1]+n[2]>0){
last=0;
a[0]=1;
for(int i=0;i<3;++i){
last2=last+v[i]*n[i];//硬币是没有最大数的限制的,所以不需要min(maxN
memset(b,0,sizeof(int)*(last2+1));
for(int j=0;j<=last;++j)
for(int k=0;k<=n[i]&&j+k*v[i]<=last2;++k)b[j+k*v[i]]+=a[j];
memcpy(a,b,sizeof(int)*(last2+1));
last=last2;
}
int result=0;
for(result=0;result<=last2;++result)//找到第一个达不到价值(有可能从0到last2都能达到,所以不能中间输出后break
if(!a[result])break;
printf("%d\n",result);
}
return 0;
}
-----------------------------------------------------
hdu1171
题目大意有n个物品,每个物品都有价值v和数量m,要分成两堆,使价值尽量接近,
解:也是个模板题,得到所有系数后,从总价值的一半开始往前搜索,得到第一个系数不为0的
#include<iostream>
#include<cstring>
int main(){
const int maxN=250010;
int a[maxN],b[maxN],n2[50],v[50],last,last2,n;
while(scanf("%d",&n)&&n>=0){
for(int i=0;i<n;++i)scanf("%d%d",&v[i],&n2[i]);
a[0]=1;
last=0;
for(int i=0;i<n;++i){
last2=last+v[i]*n2[i];
memset(b,0,sizeof(int)*(last2+1));
for(int j=0;j<=last;++j)
for(int k=0;k<=n2[i]&&j+k*v[i]<=last2;++k)b[j+k*v[i]]+=a[j];
memcpy(a,b,sizeof(int)*(last2+1));
last=last2;
}
int result=last>>1;
while(!a[result])--result;
printf("%d %d\n",last-result,result);
}
return 0;
}
-----------------------------------------------------------
hdu1398
题目大意:有几种硬币价值为1的平方到17的平方,每种硬币数量不限,问价值n的组成方法有几种
模板题
#include<iostream>
#include<cstring>
int main(){
const int maxN=301;
int a[maxN]={1},b[maxN],n;
for(int i=1;i<=17;++i){
memset(b,0,sizeof(b));
for(int j=0;j<maxN;++j)
for(int k=0;j+k*i*i<maxN;++k)b[j+k*i*i]+=a[j];
memcpy(a,b,sizeof(b));
}
while(scanf("%d",&n)&&n)printf("%d\n",a[n]);
return 0;
}
---------------------------------------
hdu2152
中文题,这次每个价值有上下限了
#include<iostream>
#include<cstring>
int main(){
const int maxN=101;
int a[maxN],b[maxN],n1[maxN],n2[maxN],last,last2,n,m;
while(scanf("%d%d",&n,&m)!=EOF){
for(int i=0;i<n;++i)scanf("%d%d",&n1[i],&n2[i]);
last=0;
a[0]=1;
for(int i=0;i<n;++i){
last2=last+n2[i]<m?last+n2[i]:m;
memset(b,0,sizeof(int)*(last2+1));
for(int j=0;j<=last;++j)
for(int k=n1[i];k<=n2[i]&&j+k<=last2;++k)b[j+k]+=a[j];
memcpy(a,b,sizeof(int)*(last2+1));
last=last2;
}
if(last>=m&&a[m])printf("%d\n",a[m]);
else printf("0\n");
}
return 0;
}
-----------------------------
hdu1709
题目大意:给你几个砝码,左右两边都能放,问哪些质量不能被称到
解:
#include<iostream>
#include<cmath>
#include<cstring>
int main(){
const int maxN=10001;
int a[maxN],b[maxN],v[100],result[100],last,last2,n,cnt;
while(scanf("%d",&n)!=EOF){
for(int i=0;i<n;++i)scanf("%d",&v[i]);
a[0]=1;
last=0;
for(int i=0;i<n;++i){
last2=last+v[i];
memset(b,0,sizeof(int)*(last2+1));
for(int j=0;j<=last;++j)
for(int k=0;k<=1&&j+k*v[i]<=last2;++k){
b[j+k*v[i]]+=a[j];
b[abs(j-k*v[i])]+=a[j];
}
memcpy(a,b,sizeof(int)*(last2+1));
last=last2;
}
cnt=0;
for(int i=1;i<=last;++i)
if(!a[i]){
result[cnt]=i;
++cnt;
}
printf("%d\n",cnt);
for(int i=0;i<cnt;++i){
printf("%d",result[i]);
if(i==cnt-1)printf("\n");
else printf(" ");
}
}
return 0;
}
-----------------------------------------
hdu2069
题目大意:有1,5,10,25,50这几种硬币,要组成价值n有几种方案,用的硬币数量加起来不能超过100个
解:这是一道二维的母函数,数据量不大,控制一下硬币的数量和价值,硬肛就行了
#include<iostream>
#include<cstring>
int main(){
int a[251][101]={1},b[251][101]={},v[]={1,5,10,25,50},result[251]={1},n;
for(int i=0;i<5;++i){
for(int j=0;j<=250;++j)
for(int k=0;j+k*v[i]<=250;++k)
for(int p=0;k+p<=100;++p)b[j+k*v[i]][k+p]+=a[j][p];
for(int j=0;j<=250;++j)
for(int k=0;k<=100;++k){
a[j][k]=b[j][k];
b[j][k]=0;
}
}
for(int i=1;i<=250;++i)
for(int j=0;j<=100;++j)result[i]+=a[i][j];
while(scanf("%d",&n)!=EOF)printf("%d\n",result[n]);
return 0;
}
---------------------------------
hdu2065
解:指数型生成函数,比较难一点,取n个为 次方,但是取哪个都无所谓,
,最后要用泰勒公式化简
指数型生成函数
由泰勒公式
所以x的n次方的系数
#include<iostream>
const int mod=100;
//快速幂
int quickMod(int a,long long int b){
int result=1;
while(b){
if(b&1)result=result*a%mod;
a=a*a%mod;
b>>=1;
}
return result;
}
int main(){
int t;
long long int n;//指数型生成函数(x+x^2/(2!)+x^4/(4!)+....)^2(1+x+x^2/(2!)+x^3/(3!)+....)^2
//由泰勒公式e^x=1+x+x^2/(2!)+x^3/(3!)+.... e^(-x)=1-x+x^2/(2!)-x^3/(3!)+....
//原式=((e^x+e^(-x))/2)^2 *e^2x=((e^2x+1)/2)^2=(e^4x+2e^2x+1)/4=(1+4x+(4x)^2/2!+....+2(1+2x+(2x)^2/2!)+....+1)/4
//=(sigma(i=0到n-1)(4^i+2*2^i)/i!)x^i/4+1/4=sigma(i=0到n-1)(4^(i-1)+2^(i-1))x^i+1/4
//所以x的n次方的系数 4^(n-1)+2^(n-1)
while(scanf("%d",&t)&&t){
for(int i=1;i<=t;++i){
scanf("%lld",&n);
printf("Case %d: %d\n",i,(quickMod(4,n-1)+quickMod(2,n-1))%mod);
}
printf("\n");
}
return 0;
}
-------------------------------------------------
hdu1521
指数型母函数
#include <iostream>
#include<cstring>
int main(){
const int maxN=11;
long long int fact[maxN]={1};
for(int i=1;i<maxN;++i)fact[i]=i*fact[i-1];
int n,m,num[maxN];
double a[maxN],b[maxN];
while(scanf("%d%d",&n,&m)!=EOF){
for(int i=0; i<n; i++)scanf("%d",&num[i]);
memset(a,0,sizeof(a));
a[0]=1;
for(int i=0; i<n; i++){
memset(b,0,sizeof(b));
for(int j=0; j<=m; j++)
for(int k=0; k<=num[i]&&k+j<=m; k++)
b[k+j]+=a[j]/fact[k];
memcpy(a,b,sizeof(b));
}
printf("%.lf\n",a[m]*fact[m]);
}
return 0;
}
hdu1028,2079,2082,2110都可以去练一下