HDOJ1028 Ignatius and the Princess III【母函数(类似的题还有1085、1398)】

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值