生成函数,英文是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
有一点特别注意一下就是初始值的问题,指数为零的初始值设为1,其他都为零,因为你要满是指数为零的系数为1 要不你的多项式相乘,不就没什么意思了吗?,当然最后,0的系数,不加进去,零不算单词
memcpy(a,b,sizeof(b));
#include
#include
#include
#include
#include
#include
#include
#include
#include
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 //2019051463
#include
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;
}
来自HDU1028
同样,我就不复制题面了
#include
#include
#include
#include
#include
#include
#include
#include
#include
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;
}
来自HDU1085
题意分析:就是 有1 2 5三种面值钱,输入,三个数分别表示,1,2,5的个数,求最小的不能用这些钱组成的数,
思路分析:这个题目明显比前两个难,就是没有给出明确的界限,然后就是不好限制他,所以这个题的难点就是找到那个限制点,剩下的就与上面相同了,首先,从1开始,他的边界一定就是1个数,2的边界就是原本那个数+22的个数,同样5也是这样,
所以代码就是这样子的
#include
#include
#include
#include
#include
#include
#include
#include
#include
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
博客链接