【DP】poj 1276 Cash Machine

Cash Machine
Time Limit: 1000MS Memory Limit: 10000K
Total Submissions: 27234 Accepted: 9671

Description

A Bank plans to install a machine for cash withdrawal. The machine is able to deliver appropriate @ bills for a requested cash amount. The machine uses exactly N distinct bill denominations, say Dk, k=1,N, and for each denomination Dk the machine has a supply of nk bills. For example, 

N=3, n1=10, D1=100, n2=4, D2=50, n3=5, D3=10 

means the machine has a supply of 10 bills of @100 each, 4 bills of @50 each, and 5 bills of @10 each. 

Call cash the requested amount of cash the machine should deliver and write a program that computes the maximum amount of cash less than or equal to cash that can be effectively delivered according to the available bill supply of the machine. 

Notes: 
@ is the symbol of the currency delivered by the machine. For instance, @ may stand for dollar, euro, pound etc. 

Input

The program input is from standard input. Each data set in the input stands for a particular transaction and has the format: 

cash N n1 D1 n2 D2 ... nN DN 

where 0 <= cash <= 100000 is the amount of cash requested, 0 <=N <= 10 is the number of bill denominations and 0 <= nk <= 1000 is the number of available bills for the Dk denomination, 1 <= Dk <= 1000, k=1,N. White spaces can occur freely between the numbers in the input. The input data are correct. 

Output

For each set of data the program prints the result to the standard output on a separate line as shown in the examples below. 

Sample Input

735 3  4 125  6 5  3 350
633 4  500 30  6 100  1 5  0 1
735 0
0 3  10 100  10 50  10 10

Sample Output

735
630
0
0

Hint

The first data set designates a transaction where the amount of cash requested is @735. The machine contains 3 bill denominations: 4 bills of @125, 6 bills of @5, and 3 bills of @350. The machine can deliver the exact amount of requested cash. 

In the second case the bill supply of the machine does not fit the exact amount of cash requested. The maximum cash that can be delivered is @630. Notice that there can be several possibilities to combine the bills in the machine for matching the delivered cash. 

In the third case the machine is empty and no cash is delivered. In the fourth case the amount of cash requested is @0 and, therefore, the machine delivers no cash.

Source


原文地址


提示:动态规划,多重背包

 

题目大意:

有各种不同面值的货币,每种面值的货币有不同的数量,请找出利用这些货币可以凑成的最接近且小于等于给定的数字cash的金额。


初始思路:

多重背包问题,第i种面额d[i]有 n[i]+1种选择方案

可以转化为01背包问题处理

转化的大概思路就是把 每种面值乘以其不同的个数,把得到的不同金额作为一件新的独一无二的货币,但是这样存在两个问题,一是 d[i]*ki 可能等于 d[j]*kj  ,其中ki ∈n[i],kj∈n[j],二是这样做一定TLE超时(前人经验O(∩_∩)O哈哈~我就不重滔覆辙呐)


解题思路:

首先解决存储问题,cash上限实在太大了..10W,推荐用new,最后记得delete就是了,不然就算AC掉,Memory也会很大= =

说到空间的申请,说点题外话,这题不像POJ1837那么BT(做过1837的同学就知道了…1837连申请动态空间都很困难,因为空间的动态值是由3个变量的最大值决定的= =有时间浪费在找最大值,宁愿申请静态空间,反正才1W)


言归正传,下面的解题思路最好配合我写的程序去看,不然很难看懂,而且看之前请保证你对01背包、完全背包、多重背包的理论知识有所了解。。。不然应该看不懂的。。。

 

本题输入有4个部分:

cash  //背包容量(最大可取金额)

N    //物品种数(面额种数)

n[i]第i种物品的个数(第i种面额的数量)

d[i]第i种物品的价值(第i种面额的价值)

 

值得注意的是,如果设每个物品的价值为w[i],体积(可理解为消耗的价值)为c[i]

那么必有 d[i] = w[i] = c[i]

 

如果把一个金额看为一种状态,那么一共有0~cash种状态

显然其中可能会发生的状态范围为min{w[i] | 1<=i<=n[i]} ~ Cash

那么可以建立一个状态数组dp[cash+1],

其中dp[j]记录的是“最接近状态j,且<=j”的状态值,即dp[j] <=j

J越接近dp[j],dp[j]的解越优,最理想是dp[j]=j

需要注意的是,dp[j]为了说明的是状态j可以发生,但不会理会dp[j]怎样发生

例如有3张1元,1张3元

那么我们既可以认为dp[3]=3是通过取3次1元得到,也可以认为dp[3]是通过取1次3元得到的,但无论怎样得到,dp[3]=3都会发生,

再需要注意的是,dp[j]的状态值会累积

再形象举例说明:例如有1张3元,cash=4

那么根据前面的说明,自然有dp[3]=dp[4]=3,就是说状态3、状态4都可以通过取1次3元发生,一旦发生,这个状态值3就会被保有在当前的状态dp[4],这其实是起到一个优化作用,后面详细解释

再接上例,当我们增加一个条件“有3张1元”,那么在已经被保存的前提“dp[4]=3”下,我们只需要通过取1次1元,就能得到比dp[4]=3的更优解dp[4]=4,但此时我们完全不用理会dp[4]=3是怎样来的,我们只需要知道dp[4]=3一定会出现就足够了

 

然后说说刚才提到的“优化问题”

利用01背包的知识,不难理解 状态方程 dp[j]=dp[ j-c[i] ]+w[i]

至于说方程是什么含义,看过01背包的大概也知道什么意思,没看的同学就别怪我不解释咯O(∩_∩)O

优化是因为状态值被记录了,就是说我们在取得下一个货币i之前,当前的状态为j-c[i]

一旦我们选取货币i,就会得到状态(j-c[i])+c[i],即状态j 。且状态j的状态值dp[j]会加上w[i]。 至于dp[j]原来值是多少,就无需理会,因为dp[j]的值是累积下来的,同样本次加上w[i]后,只要dp[j]值未到最优,它同样会成为下一次的累加值。

这样每次都直接调用前一次已获得的状态值,就可以节省一堆搜索的时间,这是DFS或BFS办不到的,也是动态规划的优点。

 

接下来说说计数器count[j]

在我的程序中,每更换一次面额,计数器会清零,这样做是为了 以面额i的价值w[i]为权,根据选取该面额的个数,对状态值dp[j]进行w[i]的整数倍分割,这样就能得到对于每组状态dp[w[i]*k]到dp[w[i]*(k+1)] (0<=k<=n[i])之间,在当前面额w[i]下的最优状态值。

例如有 3张3元

自然dp[0]=dp[1]=dp[2]=0,count[0]= count[1]= count[2]=0这是因为最小面额为3

不难得到dp[3]=dp[4]=dp[5]=3,count[3]= count[4]= count[5]=1这是因为面额3元不可分,这3个状态的最优值就是取1次3元

dp[6]=dp[7]=dp[8]=6  , count[6]= count[7]= count[8]=2这3个状态的最优值就是取2次3元

dp[9]=dp[10]=dp[11]=dp[…]=9 ,count[9]= count[10]= count[[11]= count[…]=3  最多只有3张3元,以后的的状态的最优值均为9

 

最后说说用memset函数初始化的问题

程序中也说得很清楚了,由于是动态空间,不要用sizeof,要人为计算空间大小,记得+1

我认为这是memset函数的缺陷。。。(不敢叫BUG,哈O(∩_∩)O)

 

//Memory Time 
//1052K  47MS 

#include<iostream>
using namespace std;

int main(int i,int j)
{
	int N;   //物品种数(面额种数)	
	int cash;  //背包容量(最大可取金额)
	while(cin>>cash>>N)
	{
		/*Input*/

		int* n=new int[N+1];  //n[i]第i种物品的个数(第i种面额的数量)
		int* w=new int[N+1];  //w[i]第i种物品的价值(第i种面额的价值)
		int* c=new int[N+1];  //c[i]第i种物品的体积(第i种面额消耗的价值)
		int* dp=new int[cash+1];  //dp[j]记录的是 当前最接近状态j且<=j的值,dp值会累积
		int* count=new int[cash+1];//计数器,限制某种物品(面额)的选取个数

		for(i=1;i<=N;i++)
		{
			cin>>n[i]>>w[i];
			c[i]=w[i];    //本题的单个物品的“体积”等于其“价值”
		}
		
		/*Initial*/

		memset(dp,0,4*(cash+1));  //由于dp申请的是动态内存,用sizeof计算长度会出错
		                          //这里要用 类型大小*个数,这里为 int*(cash+1) , int大小为4

		/*DP*/
		
		for(i=1;i<=N;i++)
		{
			memset(count,0,4*(cash+1));  //每更换一次面额,计数器清零
			for(j=w[i];j<=cash;j++)      //对于第i种货币,其面额d[i]~cash间任一个状态都可能发生
				if(dp[j]<dp[j-c[i]]+w[i] && count[j-c[i]]<n[i]) //count[j-c[i]]<n[i]
				{                                               //取某种面额前,必须保证这次操作之前所取该种面额的次数小于n[i]
					dp[j] = dp[j-c[i]]+w[i];    //选取第i个物品后,背包容量(允许取的最大金额)减少c[i]
					count[j]=count[j-c[i]]+1;   //对于当前状态j,第i种面额被抽了count[j]次
				}
		}

		/*Output*/
		
		cout<<dp[cash]<<endl;

		/*Release Space*/

		delete n;
		delete w;
		delete c;
		delete dp;
		delete count;
	}
	
	return 0;
}


原文写的太好了,没有任何可以补充说明的地方,现粘上我的代码:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main()
{
	int cash,n;
	while(scanf("%d%d",&cash,&n)!=EOF)
	{
		int i,j,k,num,w;
		int *dp,*count;
		dp = (int *)malloc(sizeof(int)*(cash+1));
		count=(int *)malloc(sizeof(int)*(cash+1));
		//memset(dp,0,sizeof(dp));
		memset(dp,0,4*(cash+1));
		for(i=1;i<=n;i++)
		{
			scanf("%d%d",&num,&w);
			//memset(count,0,sizeof(count));
			memset(count,0,4*(cash+1));
			for(j=w;j<=cash;j++)
			{
				if((dp[j]<dp[j-w]+w)&&count[j-w]<num)
				{
					dp[j]=dp[j-w]+w;
					count[j]=count[j-w]+1;
				}
			}
		}
		printf("%d\n",dp[cash]);
		free(dp);
		free(count);
	}
	return 0;
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值