生成函数

定义

生成函数,英文是Generating Function。恕本人不才,本文只介绍生成函数的其中一种用法。

生成函数是说,构造这么一个多项式函数g(x),使得x的n次方系数为f(n)。

对于母函数,我看到最多的是这样两句话:

  • 1.“把组合问题的加法法则和幂级数的乘幂对应起来。”

  • 2.“把离散数列和幂级数一 一对应起来,把离散数列间的相互结合关系对应成为幂级数间的运算关系,最后由幂级数形式来确定离散数列的构造。 “

其实这两句话我也不算太懂。先放这里,说不定以后可能会慢慢理解吧。

一个小栗子

  • 还是先举个大牛博客中的例子吧:

  • 有1克、2克、3克、4克砝码各一枚,问你能称出哪几种重量?每种重量各有几种方案?

下面是用母函数解决这个问题的思路:

  • 首先,我们用X表示砝码,X的指数表示砝码的重量。那么,如果用函数表示每个砝码可以称的重量,

  • 1个1克的砝码可以用函数X^0 + X^1表示,

  • 1个2克的砝码可以用函数X^0 + X^2表示,

  • 依次类推。

  • 如果我们把上面2个多项式相乘,可以得到X^0 + X^1 + X^2 + X3。继续把它与X0 + X3相乘,得到X0 + X^1 + X^2 + 2*X^3 + X^4 + X^5 + X^6。

  • 聪明的你,是不是发现了什么?

  • 如果没有,接着把它与X0+X4相乘,最后得到X^0 + X^1 + X^2 + 2X^3 + 2X^4 + 2X^5 + 2X^6 + 2*X^7 + X^8 + X^9 + X^10。

  • 由于X的指数表示的是重量,所以,在相乘时,根据幂的运算法则(同底幂相乘,指数相加),得到的结果正是所有的方案。而且,每个X前面的系数代表它有几种方案。

  • 真是神奇啊。。。。

  • 需要注意的是,如果有2个1克的砝码,应该用X^0 + X^1 + X2表示,而不是X0 + 2*X^1。

  • 母函数还可以解决其他问题,比如,整数划分。

  • 整数划分是个很经典的问题,划分规则就不再细述,直接说思路。与上面的问题相比,每种砝码的个数不再是1个,而是无限个。于是,

  • 1克的砝码可以用X^0 + X^1 + X^2 + X^3 ……表示,

  • 2克的砝码可以用X^0 + X^2 + X^4 + X^6……表示,

  • 3克的砝码可以用X^0 + X^3 + X^6 + X^9……表示,

  • 依次类推。

  • 相乘后求出X^n的系数,就是结果。

  • 总而言之,解决此类问题,只要模拟+好2个多项式相乘就好了。

  • 大概思路是开2个数组,c1[ ]保存当前得到的多项式各项系数,c2[ ]保存每次计算时的临时结果,当每次计算完毕后,把它赋给c1,然后c2清零。

  • 计算的时候,开3层for循环。最外层,记录它正在与第几个多项式相乘。第二层,表示c1中的每一项,第三层表示后面被乘多项式中的每一项。
    orz大佬的博客,(上面都来自大佬写的)
    原文链接:https://blog.csdn.net/dgq8211/article/details/7385718

3个糖栗子

[A 找单词]

  • 来自 HDU2082
  • 题面我就不复制了 ,想看点开链接看就行了。(没设密码)这两天可以来做做呦
  • 题意就是尹文字符A-Z分别代表1-26,然后输入26个数分别代表多少个A-Z,然后求一共有多少种组合方式,<mark>使得和小于等于50</mark>

母函数

  • 思路分析 : 就是利用生成函数,求出x1–50的系数,模拟一下多项式相乘,不明白的话看上面的小栗子,嘻嘻。具体而言就是三层循环
    第一层,循环从1-26,这26个字母当然可以吧他当成单价之类的东西,
    第二层,就是0-50 也就是 要求得的指数(就是组成的数) 你可能会问为什么是50,因为题中要求 不超过50了,所以50以后的没必要在进行枚举了。
    第三层,循环对应字母有几个,同时也可以优化一下,如果在枚举完字母个数之前指数已经大于50 直接进行下一层循环。

<mark>有一点特别注意一下就是初始值的问题,指数为零的初始值设为1,其他都为零,因为你要满是指数为零的系数为1 要不你的多项式相乘,不就没什么意思了吗?,当然最后,0的系数,不加进去,零不算单词</mark>

  • 结合代码理解一下吧。
    中间有一个
memcpy(a,b,sizeof(b));
  • 其实就是一个相当复制的函数,大家应该懂,不懂得话就csdn吧。
  • 还有每次把b复制给a,把b清零,是将上一个字母的结果保存到a数组里,清零是下一次进行可以前面字母没放过,直接放这个字母。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
#include <string>
#include<iostream>
#include<stdio.h>
#include<string.h>
#include <algorithm>
typedef long long ll;
using namespace std;
const int maxn=1e6+1010;
#define inf 0x3f3f3f3f
const int mod=998244353;

ll n,m,t,ans;
ll a[55],b[55],num[55];

int main() {
// memcpy(a,b,sizeof(b));//将b复制到a数组
	cin>>t;
	while(t--){
		ll sum=0;
		for(int i=1;i<=26;i++)	
			cin>>num[i];//单词系数 
		memset(a,0,sizeof(a));
		memset(b,0,sizeof(b));
		a[0]=1;//初始值
		for(int i=1;i<=26;i++)//单词 
		{
			for(int j=0;j<=50;j++)//系数 
			{
				for(int k=0;k<=num[i]&&j+i*k<=50;k++)//次数 就是有几个这样的单词 
					b[j+i*k]+=a[j];
			}
			memcpy(a,b,sizeof(b));//把b中已经计算过的值复制到a数组上就是他现在的系数 
			memset(b,0,sizeof(b));//把b数组清零开始下一个单词的计算 
		 } 
		for(int i=1;i<=50;i++)	sum+=a[i];
		cout<<sum<<endl;; 
	}
	return 0;
}

另一解法背包方案数

感谢涛涛大佬提供。
直接出代码吧,好理解点
其实a数组开不开无所谓,开了好理解点。

#include<stdio.h>//2019051463
#include<string.h>
int main()
{
	int t,a[27],d[55],b[27],i,j,k;
	for(i=1;i<=26;i++)
	a[i]=i;
	scanf("%d",&t);
	while(t--)
	{
		int sum=0;
		memset(d,0,sizeof(d));
		d[0]=1;
		for(i=1;i<=26;i++)
		scanf("%d",&b[i]);
		for(i=1;i<=26;i++)
		{
			for(j=50;j>=a[i];j--)
			{
				for(k=1;k<=b[i];k++)
				{
					if(j-k*a[i]>=0)
                        d[j]+=d[j-k*a[i]];
				}
			}
        }
		for(i=1;i<=50;i++)
            sum+=d[i];
		printf("%d\n",sum);
	}
	return 0;
 }

[E - Ignatius and the Princess III]

来自HDU1028
同样,我就不复制题面了

  • 题意分析:给出一个数,让你求出组成这个数的方式几种,(当然只有加啦)
    写下题中的小栗子便于理解
    4=4;
    4=1+3
    4=2+2
    4=1+1+2;
    4=1+1+1+1;
    所以组成四的方式有5种
    ==题中还给出了输入数的范围最大是120,
  • 思路分析 这个题个上个题没啥大差距;就是界限改了改,因为最大是120所以直接把界限改成原本的就行了,当然害的按要求,人家的输入输出可不一样要求。只能说大体结构都一样
    看代码吧,其实没有代码也行,跟上面那个题的代码一样
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
#include <string>
#include<iostream>
#include<stdio.h>
#include<string.h>
#include <algorithm>
typedef long long ll;
using namespace std;
const int maxn=1e6+1010;
#define inf 0x3f3f3f3f
const int mod=998244353;

ll n,m,t,ans;
ll a[125],b[125];

int main() {
		ll sum=0;
		memset(a,0,sizeof(a));
		memset(b,0,sizeof(b));
		a[0]=1;//初始值
		for(int i=1;i<=120;i++)
		{
			for(int j=0;j<=120;j++)
			{
				for(int k=0;j+i*k<=120;k++)
					b[j+i*k]+=a[j];
			}
			memcpy(a,b,sizeof(b));//把b中已经计算过的值复制到a数组上就是他现在的系数 
			memset(b,0,sizeof(b));//把b数组清零开始下一个单词的计算 
		 }
		while(~scanf("%lld",&n)){
			cout<<a[n]<<endl;
		}
	return 0;
}

当然这个也可以用背包方案数。

[C - Holding Bin-Laden Captive!]

来自HDU1085

  • 题意分析:就是 有1 2 5三种面值钱,输入,三个数分别表示,1,2,5的个数,求最小的不能用这些钱组成的数,

  • 思路分析:这个题目明显比前两个难,就是没有给出明确的界限,然后就是不好限制他,所以这个题的难点就是找到那个限制点,剩下的就与上面相同了,首先,从1开始,他的边界一定就是1个数,2的边界就是原本那个数+22的个数,同样5也是这样,

  • 所以代码就是这样子的

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
#include <string>
#include<iostream>
#include<stdio.h>
#include<string.h>
#include <algorithm>
typedef long long ll;
using namespace std;
const int maxn=1e6+1010;
#define inf 0x3f3f3f3f
const int mod=998244353;

ll n,m,t,ans;
ll a[8010],b[8010],num[55],c[4]= {0,1,2,5};

int	main() {
	while(scanf("%lld%lld%lld",&num[1],&num[2],&num[3])&&(num[1]||num[2]||num[3])) {
		memset(a,0,sizeof(a));
		memset(b,0,sizeof(b));
		ans=0;
		a[0]=1;
		for(int i=1; i<=3; i++) {
			ans+=num[i]*c[i];//这个就是那个边界。下面的与前两个题都一样了
			for(int j=0; j<=ans; j++) {
				for(int k=0; k<=num[i]&&j+k*c[i]<=ans; k++) {
					b[j+k*c[i]]+=a[j];
				}
			}
			memcpy(a,b,sizeof(b));
			memset(b,0,sizeof(b));
		}
		for(int i=1;;i++) if(a[i]==0) {
				cout<<i<<endl;
				break;
			}
	}
	return 0;
}

指数型母函数

来自大佬-JK Chen
博客链接

常用公式




这个是扩展