POJ 1742 :Coins (思维,多重背包转移优化)

题目大意:有 n 种硬币,每种硬币有一定的数量a[i]和面值c[i],问你 1 ~ m内的所有面值,哪些面值可以由这些硬币组成。

显然是多重背包问题,多重背包指的是每种物品具有有限个数量的背包问题。可以将每种物品单个拆开变成一个变成0 1背包问题,但这题数据太大这种拆解方法无法通过。更加优秀的方法是以 2进制形式拆解每一种物品,然后变成0 1背包,复杂度为 n ∗ m ∗ l o g ( a [ i ] ) n * m * log(a[i]) nmlog(a[i]),仍然无法通过。还可以用单调队列优化使复杂度降为 n ∗ m n * m nm(待补)

由于题目问的是是否存在解,而不是最优解,朴素的背包拆解转移方程为:dp[k] = dp[k] | dp[k - c[i]]
,k 是面值,c[i]表示第i枚硬币的面值。dp[k] = 1的情况有两种:一是在使用第 i 枚硬币之前dp[k] 已经为1,二是使用了第i枚硬币后 dp[k]才为1。

对于dp[k]已经为1的状态k不需要再进行转移计算,需要计算的是使用了第 i枚硬币之后才为 1的状态。

(学习完全背包之前,凭着01背包的记忆我一直以为完全背包的转移需要三维:第一维枚举物品,第二维背包空间,第三位枚举物品个数,上限是当前空间能装的最大个数,这样复杂度太大。学习完全背包之后,我发现它的转移充分利用了过去状态的解,使得转移降了一维,完全背包其实只是多重背包的特例,因为完全背包虽然物品无限但空间有限,每种物品实际上最多能装的个数是可以计算出来的,而不是无限个,这说明某些情况下多重背包也可以考虑像完全背包那样转移,但多重背包需要考虑的不只是能不能装得下,还有该物品的数量,这就需要多记忆一个数组,用来记忆每个状态使用了多少个这种物品,在使用数量不超过物品个数的情况下转移,这题的优化同样可以这样搞,省略一维拆解,复杂度变成O(n * m))

(这种优化只能得到可行解,得不到最优解,也就是说如果要求最优解不能用这种转移方法)

(使用二进制拆解法的多重背包问题)

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
const int maxn = 1e6 + 100;
int num[maxn],c[maxn],n,m;
bool f[maxn];
int main() {
	while(~scanf("%d%d",&n,&m) && n && m) {
		for(int i = 0; i <= m; i++) f[i] = 0;
		for(int i = 1; i <= n; i++)
			scanf("%d",&c[i]);
		for(int i = 1; i <= n; i++)
			scanf("%d",&num[i]);
		f[0] = true;
		for(int i = 1; i <= n; i++) {
			int j;
			for(j = 1; j < num[i]; j <<= 1) {
				num[i] -= j;
				for(int k = m; k >= 0; k--) {
					if(k >= j * c[i])
						f[k] |= f[k - j * c[i]];
				}
			}
			if(num[i]) {
				for(int k = m; k >= 0; k--) {
					if(k >= c[i] * num[i])
						f[k] |= f[k - num[i] * c[i]];
				}
			}
		}
		int ans = 0;
		for(int i = 1; i <= m; i++) {
			ans += f[i];
		}
		printf("%d\n",ans);
	}
	return 0;
}

(只转移不为1的状态,类似完全背包的转移优化)

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
const int maxn = 1e5 + 100;
int num[maxn],c[maxn],n,m,used[maxn];
bool f[maxn];
int main() {
	while(~scanf("%d%d",&n,&m) && n && m) {
		for(int i = 0; i <= m; i++) f[i] = false;
		for(int i = 1; i <= n; i++)
			scanf("%d",&c[i]);
		for(int i = 1; i <= n; i++)
			scanf("%d",&num[i]);
		f[0] = true;
		for(int i = 1; i <= n; i++) {
			for(int j = 0; j <= m; j++) used[j] = 0;
			for(int j = c[i]; j <= m; j++) {
				if(!f[j] && f[j - c[i]] && used[j - c[i]] < num[i]) {
					f[j] = true;
					used[j] = used[j - c[i]] + 1;
				}
			}
		}
		int ans = 0;
		for(int i = 1; i <= m; i++) {
			ans += f[i];
		}
		printf("%d\n",ans);
	}
	return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值