现在的位置: 首页 > 综合 > 正文

POJ 1276 Cash Machine (DP)

2018年01月14日 ⁄ 综合 ⁄ 共 1110字 ⁄ 字号 评论关闭

题目类型  DP

题目意思
给出一个凑钱的目标 x (0 <= x <= 100 000) 和 n (0 <= n <= 10) 种硬币的面值(1-1000) 和数量(1-1000) 问能凑出的最大的不超过 x 的钱数是多少

解题方法
多重背包
用二进制优化即可AC
二进制优化:
最终某种硬币出的数量是确定的 所以只要枚举完所有可能的数量即可 暴力枚举就是从小到大枚举数量 但是这样效率太低
因此可以把某种硬币的数量 N 分割成 c 个数 然后用这 c 个数对应的一个 "物品" 去更新其他值
那么应该分割成哪几个值呢 如果 N 等于 2^0 + 2^1 + 2^2 + 2^3 = 15 的话只要把 15 分割成 4 个整数 1 2 4 8, 那么1,2,4,8中某几个数的和
就可以表示出 1 - 15了, 即用 1, 2, 4, 8对应的物品用01背包的方式更新一次即可
但是如果硬币的数量不是 2^x-1的话 可以把 N 分成最大的 2^x-1和 (N- (2^x-1)), 2^x-1再分割成 2^0 + 2^1 + ... + 2^(x-1) 
其中 (N-(2^x-1)) < 2^x 否则 x 可以更大 这样的话 2^0,2^1,...,2^(x-1)的某几个数的和可以组合出 1->(2^x-1) 而 2^x->N可以由 (N-(2^x-1)) + (2^0,2^1,...2^(x-1))中的某几个数组成  因为(N-(2^x-1)) 是小于等于 2^x-1即要组合的某个数-(N-(2^x-1)) <= 2^x-1 所以 2^x->N自然可以组合出来
参考代码 - 有疑问的地方在下方留言 看到会尽快回复的
#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

bool dp[100010];

int main() {
//	freopen("in", "r", stdin);
	int cash, n;
	while(scanf("%d%d", &cash, &n) != EOF) {
		memset(dp, 0, sizeof(dp));
		dp[0] = true;
		for( int i=0; i<n; i++ ) {
			int a, b;
			scanf("%d%d", &a, &b);
			int k = 1; // k 表示不断增大的2的幂
			while(k <= a) {
				for( int j=cash; j>=b*k; j-- ) {
					if(dp[j-b*k]) dp[j] = true;
				}
				a -= k; // a 不断减去2的幂
				k *= 2; // 且2的幂不断增大
			}
			if(a) { // 如果还有部分剩下的话单独进行一次更新
				for( int j=cash; j>=b*a; j-- ) {
					if(dp[j-b*a]) dp[j] = true;
				}
			}
		}
		while(cash) {
			if(dp[cash]) break;
			cash--;
		}
		printf("%d\n", cash);
	}
	return 0;
}

抱歉!评论已关闭.