硬币方案 (背包DP)

题目描述:

给定 N N N 种硬币,其中第 i i i 种硬币的面值为 a i a_i ai,共有 b i b_i bi 个。从中选出若干个硬币,把面值相加,若结果为 s s s,则称“面值 s s s 能被拼成”。求 1 1 1 M M M 之间能被拼成的面值有多少个。

输入格式:

输入包含多组测试数据。
每组测试数据第一行包含两个整数 n n n M M M
第二行包含2N个整数,分别表示 a 1 ⋯ a n a_1\cdots a_n a1an b 1 ⋯ b n b_1\cdots b_n b1bn
当输入 N = 0 , M = 0 N=0,M=0 N=0,M=0 时,表示输入终止,且该数据无需处理。

输出格式:
每组用例输出一个结果,每个结果占一行。

样例输入:

3 10
1 2 4 2 1 1
2 5
1 4 2 1
0 0

样例输出:
8
4

数据范围:

1 < = N < = 100        1 < = M < = 1 0 5       1 < = a i < = c i < = 1000 1<=N<=100~~~~~~1<=M<=10^5~~~~~1<=a_i<=c_i<=1000 1<=N<=100      1<=M<=105     1<=ai<=ci<=1000


解题思路:

判断 1 ⋯ M 1\cdots M 1M 以内有多少个 S S S ,其实就是判定每个 S S S 是否可行,考虑用动态规划。

f i f_i fi 表示这些硬币能否凑出面值为 i i i 的数,若能 f i = t r u e f_i=true fi=true;否则 f i = f a l s e f_i=false fi=false
由于每个硬币有固定数量,我们当然不能一个硬币无止境的凑。因此设 c n t i , j cnt_{i,j} cnti,j 表示凑成面值为 i i i时,第 j j j 种硬币最少要多少个。

很显然,若第 k k k 种硬币能凑出 i i i,那么 f i − a k = t r u e f_{i-a_k}=true fiak=true,而 c n t i , j = c n t i − a k , j + 1 cnt_{i,j}=cnt_{i-a_k,j}+1 cnti,j=cntiak,j+1,由于每个物品可以被多次选,因此 f i f_i fi 正序枚举。

可以推出状态转移方程:

f i = { t r u e f i − a k = t r u e   &   c n t i − a k , j < b j       k = 1 ⋯ n f a l s e o t h e r s f_{i}=\begin{cases} true&f_{i-a_k=true~\&~cnt_{i-a_k,j}<b_j~~~~~k=1\cdots n}\\ \\ false&others \end{cases} fi=truefalsefiak=true & cntiak,j<bj     k=1nothers
c n t = { c n t i − a k , j + 1 f i − a k = t r u e   &   c n t i − a k , j < b j      k = 1 ⋯ n 0 o h e r s cnt=\begin{cases} cnt_{i-a_k,j}+1&f_{i-a_k=true~\&~cnt_{i-a_k,j}<b_j~~~~k=1\cdots n}\\ \\ 0&ohers \end{cases} cnt=cntiak,j+10fiak=true & cntiak,j<bj    k=1nohers


CODE:

#include <iostream>
#include <cstring>
using namespace std;
int n,m,a[110],b[110];
bool f[100010]={false};
int cnt[100010]={0};
int main()
{
	cin>>n>>m;
	while(n!=0||m!=0)
	  {
	  	for(int i=1;i<=n;i++) cin>>a[i];
	  	for(int i=1;i<=n;i++) cin>>b[i];
	  	memset(f,false,sizeof(f));
	  	f[0]=true;
	  	cnt[0]=0;
	  	for(int i=1;i<=n;i++)
	  	  {
	  	  	 memset(cnt,0,sizeof(cnt)); //当场更新 cnt ,可以省掉第二维硬币的种类
	  	     for(int j=a[i];j<=m;j++)
			   {
			   	  if(f[j]==false&&f[j-a[i]]==true&&cnt[j-a[i]]<b[i])
			   	    {
			   	    	f[j]=true;
			   	    	cnt[j]=cnt[j-a[i]]+1;
				    }
			   }	
		  }
		int ans=0;
		for(int i=1;i<=m;i++)
		  if(f[i])
		    ans++;
		cout<<ans<<endl;
	  	cin>>n>>m;
	  }
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值