【HDU2844】二进制优化多重背包

1.题目链接。首先这个题的意思就是:n种硬币,每种硬币有面值和数量,给你m,让你求从1到m这m个数据,有多少个可以用这些硬币凑出来。

2.我们分析一下这个问题,似乎是一个背包问题,但是这个是让我们求方案数,而不是最大能够装多少之类的。我们还是按照我们之前的DP方程,在最朴素的多重背包问题种,DP[k]代表容量为k的时候我们最多可以装多少东西,和这个问题好像没啥关系。那么现在就有两个解决方案:把这个问题的DP方程和这个问题联系起来/重新设一个DP方程。到底哪一个跟家合适呢?

3.我们仔细思考一下,DP[k]代表背包容量为k的时候我们最多能够装的东西的价值/重量。对应到这个问题上,其实就是DP[k]最多能够凑出来的钱数。停一下,我们似乎发现了一个很有用的结论:这个问题就是在用钱凑钱,我们的DP[k]刚好就是容量为k的时候,能够凑出来的最多的钱。那么只要他们相等,就说明k是可以被凑出来的。这也就解释了我们最后边的判断条件。很多博客虽然写了这个,但是没有说明原因,我们这里说明原因(因为大佬们觉得这是常识,不用说明,我这种菜鸡还在这废话一堆)

4.知道了这些,我们的第一发代码就出来了。就是一个裸的多重背包,OK,那么我们直接写一发上去。不幸的是T了,因为这里的A太大了。先附上我T了的代码:

#include"stdafx.h"
#include<iostream>
#include<algorithm>
using namespace std;
int n, m;
const int N = 105;
int val[N], num[N];
int dp[100000];
#pragma warning(disable:4996)
int main()
{
	while (~scanf("%d%d", &n, &m) && n&&m)
	{
		memset(dp, 0, sizeof(dp));
		for (int i = 0; i < n; i++)
		{
			scanf("%d", &val[i]);
		}
		for (int i = 0; i < n; i++)
		{
			scanf("%d", &num[i]);
		}
		for (int i = 0; i < n; i++)
		{
			for (int j = 1; j <= num[i]; j++)
			{
				for (int k = m; k >= val[i]; k--)
				{
					dp[k] = max(dp[k], dp[k - val[i]] + val[i]);
				}
			}
		}
		int ans = 0;
		for (int i = 1; i <= m; i++)
		{
			if (dp[i] == i)
				ans++;
		}
		printf("%d\n", ans);
	}
}

5.最后分析复杂度之后发现这的确会TLE。那就用二进制优化吧。首先为什么多重背包可以用二进制优化?这是从它的程序的实现上来的,我们知道多重背包第二层是在枚举每一种背包的数量,这里的枚举的意思就是:假设第i种物品有n[i]个,那么他需要比较n[i]+1次,因为他需要判断放进去0个,1个,2个,3个...n[i]每一种情况里面的最优解。那么我们很显然就会想到,我可不可以一堆一堆的放,第一次放1个,第二次2个,第三次4个....这种2的幂次方来放。那么肯定是可以的,而且很快就会找到结果。但是问题又来了,我第i个物品有n[i]个,你怎么你能保证从0到n[i]这n[i]+1个数据在你这种放的方案下每一个都能被比较到。这个请放心,因为二进制的原因,这个是可以证明的。比如8=1+2+4+1,这四个数可以组成1-8种这8个数字任意一个。知道了这些,就可以写代码了.但是依然T了,最后看了一下题解,这是一个多重背包和完全背包结合的题目。我们可以使用完全背包解决优化,加上这个优化就AC了,额有点不太容易。

#include"stdafx.h"
#include<iostream>
#include<algorithm>
const int maxn = 1e5 + 5;
using namespace std;
int a[maxn];
int num[maxn];
int dp[maxn];
int n, m;
#pragma warning(disable:4996)
int main()
{
	while (~scanf("%d%d", &n, &m) && n&&m)
	{
		memset(dp, 0, sizeof(dp));
		for (int i = 0; i < n; i++)
		{
			scanf("%d", &a[i]);
		}
		for (int i = 0; i < n; i++)
		{
			scanf("%d", &num[i]);
		}
		for (int i = 0; i < n; i++)
		{
			int c = num[i];
			if (m < c*a[i])
			{                              //判断是否可以当成完全背包问题来解决单个物品体积×件数>=容量
				for (int k = 0; k <= m; k++)          //完全背包
					if (k >= a[i])
						dp[k] = max(dp[k], dp[k - a[i]] + a[i]);
			}
			else 
			{
				for (int j = 1; j <= c; j <<= 1)
				{
					//分堆过程
					for (int k = m; k >= 0; k--)           //一次性询问2^j的01背包
						if (k >= a[i] * j)
							dp[k] = max(dp[k], dp[k - a[i] * j] + a[i] * j);
					c -= j;
				}
				if (c > 0)
				{                                      
					for (int k = m; k >= 0; k--)
						if (k >= a[i] * c)
							dp[k] = max(dp[k], dp[k - a[i] * c] + a[i] * c);
				}
			}
		}
		int ans = 0;
		for (int i = 1; i <= m; i++)
		{
			if (dp[i] == i)
				ans++;
		}
		printf("%d\n", ans);
	}
	return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值