• 【hdoj_1085】Holding Bin-Laden Captive![母函数]


    题目:http://acm.hdu.edu.cn/showproblem.php?pid=1085


    可以这样理解题意:给出1元,2元和5元的三种硬币若干,每种硬币数量给出,现在可以从所有的硬币中,选出若干(三种中的若干种,若干个)组合出一定的金额,待求的是不能组合出来的金额的最小值.


    例如

    题目给出的测试例题中,三种硬币数量分别为1,1,3,则题目抽象成数学问题为:

    (1+x)(1+x^2)(1+x^5+x^10+x^15)的乘积展开式中,按照次数递增的顺序排列,第一个系数为0的项的次数是多少次?

    展开之后,可以发现,1,x,x^2,x^3都存在,x^4不存在,所以,结果为4.

    再例如

    三种硬币的数量分别为2,1,2,则(1+x+x^2)(1+x^2)(1+x^5+x^10),展开式中1,x,...,x^14(最高次项)都存在,系数都不为0,所以,第一个系数为0的项为x^15,所以,结果为15.


    思路:给出三种硬币的数量分别为n1,n2,n5,则最大金额为max=1*n1+2*n2+5*n5,最小金额为0,如果中间缺省一些金额,则求出最小的缺省的金额,如果中间不缺省,则题目所求的最小值为max+1.

    直观理解的C++代码如下:

    #include<iostream>
    using namespace std;
    
    int main()
    {
    	int n1,n2,n5;
    	while(1)
    	{
    		cin >> n1 >> n2 >> n5;
    		if(!n1 && !n2 && !n5)//结束条件
    			break;
    
    		int c[10000] = {0};//若money表示金额,则c[money]值的意思是组成金额money共有几种可能的组合,如果为0,则不能组成这种金额.
    
    		for(int i=0;i<=n1;i++)
    			for(int j=0;j<=n2;j++)
    				for(int k=0;k<=n5;k++)//(每种硬币最少可以选择0个,最多可以选择n_i个)
    				{
    					int money = 1*i + 2*j + 5*k;//在选择i个1,j个2和k个5的情况下的金额
    					c[money] += 1;//组成金额money的可能方法+1
    				}
    		int max = 1*n1+2*n2+5*n5;//最大金额
    		int ok = 0;//开关变量
    		for(int m=0;m<=max;m++)
    			if(!c[m])//如果第m项系数为0
    			{
    				cout << m << endl;//输出这个结果
    				ok = 1;
    				break;
    			}
    		if(!ok)//如果0~max的项的系数均非0
    			cout << max+1 << endl;
    	}
    	
    	return 0;
    }



    上述代码提交,提示超时.因为,代码中含有三层循环,而每层循环的最大次数为1000次,所以最大总共循环次数为10^9.

    简化的做法是将三层循环分布化为两层循环,(A)(B)(C)可以先计算A和B组合的结果D,再计算D和C组合的结果.

    如何用代码实现?看一个例子:

    1个1元,1个2元,3个5元,则:

    首先,单独用一种硬币可以组合的金额对应的方法种类数分别为:

    只用1元的:a[0] = 1,a[1] = 1;

    只用2元的:b[0] = 1,b[1] = 0,b[2] = 1;

    只用5元的:c[0]  = 1,c[1~4] = 0,c[5] = 1,c[6~9] = 0,c[10] = 1,c[11~14] = 0,c[15]=1;

    然后,组合1元和2元的:

    d[0] = a[0]*b[0] = 1;

    d[1] = a[0]*b[1] + a[1]*b[0] = 1;

    d[2] = a[0]*b[2] = 1;

    d[3] = a[1]*b[2] = 1;

    之后,再将上面的结果与5元的组合:

    e[0] = d[0]*c[0] = 1;

    e[1] = d[1]*c[0] = 1;

    e[2] = d[2]*c[0]  =1;

    e[3] = d[3]*c[0] = 1;

    e[4] = 0(没有方法可以组合得到4)

    ......

    上述过程的C++代码如下:

    #include<iostream>
    using namespace std;
    
    int main()
    {
    	int num_1,num_2,num_5;
    	while(1)
    	{
    		cin >> num_1 >> num_2 >> num_5;
    		if(!num_1 && !num_2 && !num_5)//结束条件
    			break;
    
    		int a[10000]={0},b[10000]={0},c[10000]={0},d[10000]={0},e[10000]={0};
    		int i,j;
    		
    		//初始化:只用一种硬币,可以组合成的金额对应的方法种类数
    		for(i=0;i<=num_1;i++)
    			a[i] = 1;
    		for(i=0;i<=num_2;i++)
    			b[2*i] = 1;
    		for(i=0;i<=num_5;i++)
    			c[5*i] = 1;
    		
    		//1和2组合的结果
    		for(i=0;i<=num_1;i++)
    			for(j=0;j<=num_2;j++)
    				d[i+2*j] += a[i] * b[2*j];
    			//[注意]a[i]*b[2*j]中间的乘号:i对应a[i]种方法,2*j对应b[2*j]中方法,所以i+2*j对应a[i]*b[2*j]种方法
    
    		//1和2组合之后,再与5组合的结果
    		for(i=0;i<=num_1+2*num_2;i++)
    			for(j=0;j<=num_5;j++)
    				e[i+5*j] += d[i] * c[5*j];
    		
    		int max = num_1 + 2*num_2 + 5*num_5;
    		
    		/*输出各项系数(组合成这种金额的方法种类数)
    		for(i=0;i<=max;i++)
    			cout << e[i] << " ";
    		cout << endl;
    		*/
    
    		int ok = 0;
    		for(i=0;i<=max;i++)
    			if(!e[i])
    			{
    				cout << i << endl;
    				ok = 1;
    				break;
    			}
    		if(!ok)
    			cout << max+1 << endl;
    	}
    
    	return 0;
    }


    上述代码,提交AC.

    总结和注意:

    1.本题用到组合数学的思想,第一种代码(三层循环)有助于理解思路,第二种代码,无非将()()()的过程拆分成两部分,从而避免过多循环.在代码实现时候,可以用一个难度适中的实例,手动计算整个过程,有助于理解写代码.

    2.第二种代码中,【注意】

  • 相关阅读:
    动态多条件查询分页以及排序(一)MVC与Entity Framework版url分页版
    关于MVC3种IOC自写方法GetItems("Model名字")得到的Model为空的解决方法
    css中!important 的使用
    MVc4阅读后总结
    jquery方法off()
    关于backgroundpostion 火狐和其他浏览器的不同
    Random.Next
    sort(function(a,b){return ab})是什么
    js 简单的自制分组(类似于分页) 结合mvc3
    Discus 论坛 使用方法
  • 原文地址:https://www.cnblogs.com/tensory/p/6590761.html
Copyright © 2020-2023  润新知