生成函数入门

目录

定义

作用

模板

题目

hdu1085

hdu1171

hdu1398

hdu2152

hdu1709

hdu2069

hdu2065

hdu1521


生成函数即母函数,是组合数学中尤其是计数方面一个重要理论和工具。

定义

对于任意数列a_{0},a_{1},a_{2},a_{3}\cdots用一个函数G\left(x \right )=a_{0}+a_{1}x^{1}+a_{2}x^{2}\cdots联系起来,我们称G(x)为该数列的生成函数

-----------------------------------------------------------

作用

我们可以用生成函数来求出斐波那契数列的通项公式,解法如下:

G\left(x \right )=0+1*x^{1}+1*x^{2}+2*x^{3}+3*x^{4}+5*x^{5}+\cdots

xG\left(x \right )=0*x^{1}+1*x^{2}+1*x^{3}+2*x^{4}+3*x^{5}+5*x^{6}+\cdots

G\left(x \right )+xG\left(x \right )=0+1*x^{1}+2*x^{2}+3*x^{3}+5*x^{4}+8*x^{5}+\cdots

G\left(x \right )+xG\left(x \right ) 相当于G(x)的序列全部左移一下再去掉多余的1即G\left(x \right )+xG\left(x \right )=\frac{G\left(x \right )}{x}-1

立即推G\left(x \right )=\frac{x}{1-x-x^{2}} ,其实还可以继续化简,我就不化了

---------------------------------------------------------------

从一道简单的题目开始练起

我们有1克,2克,3克,4克的砝码各一个,还有一个天平,假设天平左边不能放砝码的话(左物右码),能称出哪些重量的物品,并求出对应方案数

解:我们用a_{n}x^{n}来表示方案,其中a_{n}表示方案数,x^{n} 表示重量

则只用1克可以称出1+x^{1} ,1表示不放

只用2克可以称出1+x^{2} ,同理3的1+x^{3} ,4的1+x^{4} ,最后因为相互独立,可以乘起来

G\left(x \right )\\=\left(1+x^{1} \right )\left(1+x^{2} \right )\left(1+x^{3} \right )\left(1+x^{4} \right )\\=1+x+x^{2}+2*x^{3}+2*x^{4}+2*x^{5}+2*x^{6}+2*x^{7}+x^{8}+x^{9}+x^{10}

例如重量为6的有1,2,3和2,4两种方案

--------------------------------------------------------------

这次我们有质量为a1,a2,a3,a4...ak克的砝码各一个,还有一个天平,左右两边都能放但是物品只能放左边,问重量为n克的物品有几种称法

解我们用x^{-a_{i}}+1+x^{a_{i}} 来表示第i个砝码,其中x^{-a_{i}} 表示质量为ai的砝码放在天平左边,1表示不放,x^{a_{i}}表示放在右边

因为相互独立乘起来就可以的到G(x),最后输出x^{n}的系数

----------------------------------------------------------

模板

下面给出模板(系数都是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} 次方,但是取哪个都无所谓,\frac{x^{n}}{n!},最后要用泰勒公式化简

指数型生成函数\left(x+\frac{x^{2}}{2!}+\frac{x^{4}}{4!}+\cdots \right )^{2}\left(1+x+\frac{x^{2}}{2!}+\frac{x^{3}}{3!}+\cdots \right )^{2}
由泰勒公式  \left\{\begin{matrix} e^{x}=1+x+\frac{x^{2}}{2!}+\frac{x^{3}}{3!}+\cdots\\ e^{-x}=1-x+\frac{x^{2}}{2!}-\frac{x^{3}}{3!}+\cdots \end{matrix}\right.

\left (\frac{e^{x}+e^{-x}}{2} \right )^{2}e^{2x}\\ =\left(\frac{e^{2x}+1}{2} \right )^{2}\\ =\frac{e^{4x}+2e^{2}+1}{4}\\ =\frac{\left(\left (1+4x+\frac{\left(4x \right )^{2}}{2!}+\cdots \right )+2\left(1+2x+\frac{\left(2x \right )^{2}}{2!}+\cdots \right )+1\right )}{4}\\ =\sum_{i=0}^{n-1}\frac{4^{i}+2*2^{i}}{i!*4}x^{i}+\frac{1}{4}\\ =\sum_{i=0}^{n-1}\frac{4^{i-1}+2^{i-1}}{i!}x^{i}+\frac{1}{4}
所以x的n次方的系数4^{n-1}+2^{n-1}

#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都可以去练一下
 

 

 

 

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Nightmare004

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值