HDOJ1028、1085、1398
母函数(生成函数)
定义:某个序列的母函数是一种形式幂级数,每一项的系数可以提供关于这个序列的信息
思想:把组合问题的加法法则和幂级数的乘幂对应起来
两个典型例子(参照网上)
1、1g、2g、3g、4g砝码各一枚,能称出哪几种重量,每种重量各有几种可能方案?
g(x) = (1+x) (1+x^2) (1+x^3) (1+x^4)
展开后,比如有一项是2x^5,说明称出5g的方案有两种,其他项表明的信息以此类推。
2、1分、2分、3分的邮票贴出不同数值的方案数
g(x) = (1+ x+ x^2 +…) (1 + x^2+ x^4+…)(1+ x^3+ x^6+…)
x^4的系数为5。4 = 4 = 3+1 = 2+2 = 2+1+1 = 1+1+1+1,所以贴出4分有五种方案。
HDOJ1028
思路:这道题和上面典型例子相似。就是给定一个数,问有多少个拆分方案。当然例子里面只有1分、2分、3分这三个数值。而这道题小于等于给定数字的数值都可以。比如给定8,那1,2,3,4,5,6,7,8都可以作为拆分的数字。
代码(模板):
#include <iostream>
using namespace std;
const int _max = 125;
int c1[_max], c2[_max]; //c1[]保存最终的,c2[]保存每次的
int main()
{
int nNum;
int i, j, k;
while(cin >> nNum)
{
for(i=0; i<=nNum; ++i) //表达式的第一个,初始化
{
c1[i] = 1;
c2[i] = 0;
}
for(i=2; i<=nNum; ++i) //从第二个表达式开始
{
for(j=0; j<=nNum; ++j) //第j个变量
for(k=0; k+j<=nNum; k+=i) //j和k是两个表达式里的(表达式指由一个括号括起的式子)
{
c2[j+k] += c1[j];
}
for(j=0; j<=nNum; ++j) //计算结果由c2暂时保存,现在赋给c1
{
c1[j] = c2[j];
c2[j] = 0;
}
}
cout << c1[nNum] << endl;
}
return 0;
}
代码理解:我看了别人的博客,理解了好久好久。但发现自己动手跟着循环演算一下才是最好的方法。现在大致说一下代码的内容(只可意会~):
1.第一个for循环做的事情是初始化
for(i=0; i<=nNum; ++i) //表达式的第一个,初始化
{
c1[i] = 1;
c2[i] = 0;
}
这道题的g(x) = (1 + x + x^2 +…) (1 + x^2+ x^4+…)(1+ x^3+ x^6+…)(1 + x^4 + x^8+…)…所以for循环初始化了第一个表达式(加粗的部分)。c1[0] = 1的意思是指数为0的系数为1,代表1那一项;c1[1] = 1的意思是指数为1的系数为1,代表x那一项,以此类推。
2.第二个for循环做的事情是第i个表达式与前面计算出的式子相乘(两个表达式相乘)
for(i=2; i<=nNum; ++i)
{
for(j=0; j<=nNum; ++j)
{
for(k=0; k+j<=nNum; k+=i)
{
c2[j+k] += c1[j];
}
}
for(j=0; j<=nNum; ++j)
{
c1[j] = c2[j];
c2[j] = 0;
}
}
两个表达式相乘,j代表前一个式子里指数为j的那一项,k代表后一个式子里指数为k的那一项。所以这两项相乘的项指数为j+k,对应的代码为c2[j+k] += c1[j]。j每次加一,因为第一个表达式就决定了0,1,2,3,4…每一个指数都有。而k每次加i,因为在第i个表达式里,每两项之间的指数相差i。比如第二个表达式,x的二次方,x的四次方指数相差2。
当两个表达式相乘计算完毕后,对应的结果存放在c2中,c2只是暂时存放结果,所以还要用一个循环把c2的值赋给c1,再把c2清零.
HDOJ1398
#include <iostream>
using namespace std;
const int _max = 100001;
int c1[_max], c2[_max];
int main()
{ //int n,i,j,k;
int nNum; //
int i, j, k;
while(cin >> nNum && nNum!=0)
{
for(i=0; i<=nNum; ++i)
{
c1[i] = 1;
c2[i] = 0;
}
for(i=2; i<=nNum; ++i)
{
for(j=0; j<=nNum; ++j)
for(k=0; k+j<=nNum; k+=i*i)
{
c2[j+k] += c1[j];
}
for(j=0; j<=nNum; ++j)
{
c1[j] = c2[j];
c2[j] = 0;
}
}
cout << c1[nNum] << endl;
}
return 0;
}
这道题相比1028只需要将代码改动一处,k+=i改成k+=i*i
HDOJ1085
#include <iostream>
using namespace std;
const int _max = 8e3+10;
int c1[_max], c2[_max];
int main()
{
int i, j, k;
int num[4];
num[1] = 1;
num[2] = 2;
num[3] = 5;
int count[4];
while(cin>> count[1] >> count[2] >> count[3]&&!(count[1]==0&&count[2]==0&&count[3]==0))
{
int n = count[1]*1+count[2]*2+count[3]*5+1;
for(i=0;i<=count[1];i++)
{
c1[i] = 1;
c2[i] = 0;
}
for(i=count[1]+1;i<=n;i++)
{
c1[i] = 0;
c2[i] = 0;
}
for(i=2;i<=3;i++)
{
for(j=0;j<=n;j++)
{
for(k=0;k+j<=n&&k<=count[i]*num[i];k+=num[i])
{
c2[j+k] += c1[j];
// printf("c2[%d] = %d\n", j+k, c2[j+k]);
}
}
for(j=0; j<=n; j++)
{
c1[j] = c2[j];
// printf("c1[%d] = %d\n", j, c1[j]);
c2[j] = 0;
}
}
for(i=0;i<=n;i++)
{
if(c1[i]==0)
{
cout<<i<<endl;
break;
}
}
}
return 0;
}
还是建立在模板代码的基础下,有一点小小的改动,主要体现在循环的边界和条件一块。这里说一下int n = count[1]*1+count[2]*2+count[3]*5+1;
我一开始没有考虑到如果给了面值总额为m的钱币,1~m都可以用面值为1,2,5表示出来的情况。这里加一的作用就是,如果这种情况发生了,那么最小的无法表示的金额为m+1