HDU 2844 Coins(多重背包,装满可行性)

                                                      HDU 2844 Coins    (POJ1742也是这道)

                                                      http://acm.hdu.edu.cn/showproblem.php?pid=2844

                                                     

题意:

       现在有价值val[1],val[2],…val[n]的n种硬币, 它们的数量分别为num[i]个. 然后给你一个m, 问你区间[1,m]内的所有数目, 由之前n种硬币来构造(即选取某些硬币使得这些硬币的价值和等于[1,m]区间的特定数), 最多能构造出这m个数中的多少个?

分析:

①:显然是背包装满问题(本题只不过是cost和value值一样了),相比于常规背包只是初始化不同,传递所装物品的最大值以外,还要传递背包知否装满,初始化为-INF(装不满),dp[0]初始化为0(装满)。这样在传递的状态的时候,没装满的背包都是负值。

②:看到有人这样定义:dp[i][j]表示只用前i种硬币且硬币总价值总价值正好等于j时, 有多少种方法.

有点像前面的这道题 数的划分

作者:http://blog.csdn.net/u013480600/article/details/40553757

但是他这个有个问题,就是~为啥dp数组里会出现负值,就是把最后的if(dp)改成if(dp>0)会wrong;

不知道是不是初始化的原因:dp[i][0]=1;

③:《挑战程序设计竞赛》有种这样的做法:

dp[i][j]:用前i种硬币就能得到价值为j的时候,第i种硬币最多能剩余多少个(不能得到的时候,就剩下-1)

递推关系:

dp[i][j]=num[i]  //dp[i-1][j]>=0,用前i-1种硬币就能达到价值i了,所以不需要第i种了

dp[i][j]=-1     //j<value[i]或者dp[i][j-value[i]]<=0。加一个第i种硬币就超过j了,或者加到j-value[i]这种硬币就没了。实际上这两个条件可以有下面的递推关系的出来(红色大于0,再根据等式~~~)

dp[i][j]=dp[i][j-value[i]]-1 

这种方法好难想哦

初始化全为-1,最后遍历一遍数组,大于等于0的就符合。


④:回顾下原始的背包问题,背包问题是让求最优值,方法1就是完全套用的多重背包的模板,只不过修改了初始化,这里我感觉有两种浪费:

1.对一种物品,假设体积为3的背包都装不满了,体积为4.5.6.7...的当然也不会装满了,都没有必要往下走了。

2.对一种物品,能装满体积为3的背包了,就没必要后面再对体积为3的背包进行判断了。

这也是在poj上,为什么直接套用多重背包模板会超时的原因吧?

为了避免繁琐的减枝,不妨直接修改dp的定义。

bool dp[j]:硬币能否凑成价值j。

下面分别是套用多重背包(杭电可以AC),方法③,方法④(推荐)的AC代码

多重背包:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
# define INF 0x3f3f3f3f
using namespace std;

int a[105], c[105];//价值,数量。
int dp[100010];

int n, m;//钱的种类,最大价格


void Complete_Pack(int cost)
{
	for (int i = cost; i <= m; i++)
	{
		dp[i] = max(dp[i], dp[i - cost] + cost);
		
	}
}
void Zero_One_Pack(int cost)
{
	for (int i = m; i >= cost; i--)
	{
		
		dp[i] = max(dp[i], dp[i - cost] + cost);
	
	}
		
}
void Muilti_Pack(int cost, int num)
{
	if (cost*num >= m)
	{
		Complete_Pack(cost); return;
	}
	int k = 1;
	while (k < num)
	{
		Zero_One_Pack(cost*k);
		num -= k;
		k *= 2;
	}
	Zero_One_Pack(cost*num);
}
int main()
{
	while (cin>>n>>m,n||m)
	{
		for (int i = 1; i <= n; i++)
			scanf("%d", &a[i]);        //价值
		for (int i = 1; i <= n; i++)
			scanf("%d", &c[i]);        //数量
		fill(dp+1, dp + m+1, -INF);
		dp[0] = 0;
		for (int i = 1; i <= n; i++)
	       Muilti_Pack(a[i], c[i]);
		int ans = 0;
		for (int i = 1; i <= m; i++)
		{
			if (dp[i]>0)
				ans++;
		}
		printf("%d\n", ans);
	}
	return 0;
	
}


方法③:

#include<iostream>  
#include<cstdio>  
#include<algorithm>  
using namespace std;
int n, m;
int a[105], c[105];
int dp[100005];
int main()
{
	while (cin >> n >> m, n && m)
	{
		for (int i = 1; i <= n; i++)cin >> a[i];//价值
		for (int i = 1; i <= n; i++)cin >> c[i];//数量
		memset(dp, -1, sizeof(dp));
		dp[0] = 0;    //并非真正的初始化
		for (int i = 1; i <= n; i++)
		{
			for (int j = 0; j <= m; j++)
				if (dp[j] >= 0)dp[j] = c[i]; //第一次循环相当于初始化。
				else if (j < a[i] || dp[j - a[i]] <= 0)dp[j] = -1;
				else dp[j] = dp[j - a[i]] - 1;
		}
		int num=0;
		for (int j = 1; j <= m; j++)
		{
			if (dp[j] >= 0)num++;
		}
		cout << num << endl;
	}
	system("pause");
}


方法④:

#include<cstdio>
#include<iostream>
#include<algorithm>

using namespace std;

int a[105], c[105];//价值,数量。
bool dp[100005];
int use[100005];


int main()
{
	int n, m, ans;
	while (~scanf("%d%d",&n,&m), n || m)
	{
		for (int i = 1; i <= n; i++)
			scanf("%d", &a[i]);
		for (int i = 1; i <= n; i++)
			scanf("%d", &c[i]);
		fill(dp + 1, dp + 1 + m, false);
		dp[0] = true;
		ans = 0;
		for (int i = 1; i <= n; i++)
		{
			fill(use, use + m + 1, 0);   //use[j]表示:凑成价值为j的时候,硬币i用了多少了。
			for (int j = a[i]; j <= m; j++)
			{
				if (!dp[j] && dp[j - a[i]] && use[j - a[i]] + 1 <= c[i])   //价值j还没凑够,j-a[i]凑够了,还有硬币(第i种)可以用。
				{                                                          //三个条件卡住,第二个循环好多没了
					dp[j] = true;
					use[j] = use[j - a[i]] + 1;
					ans++;
				}

			}
		}
		printf("%d\n", ans);
		
	}
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值