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

POJ 3260 The Fewest Coins (多重背包 + 完全背包)

2017年11月16日 ⁄ 综合 ⁄ 共 1730字 ⁄ 字号 评论关闭

题目类型  DP

题目意思
给出 n (1 <= n <= 100) 种硬币和一个数 m (m <= 10000) 其中每种硬币给出面值(1-120) 和 数量(1-10000)
现在应付的钱数是 m, 使用给出的硬币付钱后可能需要找钱 找钱的时候硬币的数量是无穷大的
问付钱时用的硬币数 + 找钱所用的硬币数 最小是多少

解题方法
多重背包 + 完全背包
付钱时用多重背包 找钱时用完全背包即可
这道题重要的地方是确定边界的大小
假设硬币中面额最大的硬币的面额是 n_max 可以证明付钱的边界最大是  n_max * n_max + m
证明如下 :
用反证法 首先假设某个最优方案付钱的数目已经大于 n_max*n_max + m 说明需要找钱的数目大于 n_max * n_max 
这时找钱的硬币数量 > n_max(因为最大的面额才是n_max) 假设找钱所用的x枚硬币是 N1, N2, ... Nx (x > n_max) 
先不管找钱所用的硬币的面额 假设 S(i) = N1+N2+...+Ni  设 Mod(i) = S(i) % n_max  
那么 Mod(1) -> Mod(x) 有 x 个数 (x > n_max)  而 Mod(i) 的取值范围是 (0->n_max-1 共n_max个数)
根据鸽巢原理其中至少有两个Mod()的值是相等的(假设是 Mod(i)和 Mod(j) 其中 j>i ) Mod()值相等意味着 ( S(j)-S(i) ) % n_max == 0 即第 i+1枚硬币到第 j枚硬币的面额加起来是 n_max的倍数 这时如果这几枚硬币的面额不全是 n_max 的话就意味着可以用数量更少的面额为n_max的硬币来代替原来的硬币
同理 付钱的硬币数量 > n_max 也可以找到几枚硬币的面额和是n_max的倍数 如果这几枚硬币不全是n_max的话所用的硬币总数同样可以更少
但是找钱和付钱的那几枚面额加起来的和是n_max的倍数的硬币都是n_max(或者都含有面额为n_max的硬币)的话 虽然都不能替换成数量更少的硬币 但是这种情况下显然有几枚面额为n_max的硬币是不需要用到的 (又给又还其实相当于不需要给) 这样所需的硬币总数还是有更少的情况 
因此假设不成立 付钱数应该是 <= n_max*n_max+m的 
这个证明不太严谨
参考代码 - 有疑问的地方在下方留言 看到会尽快回复的
#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

const int INF = 1<<29;

int dp[30000];
int dp2[30000];
int w[110], c[110];

int main() {
	freopen("in", "r", stdin);
	int n, V;
	while(scanf("%d%d", &n, &V) != EOF) {
		int n_max = -1;
		for( int i=0; i<n; i++ ) scanf("%d", &w[i]), n_max = max(n_max, w[i]);
		for( int i=0; i<n; i++ ) scanf("%d", &c[i]);
		int V2 = n_max * n_max + V; //最大的付钱数
		for( int i=0; i<=V2; i++ ) dp[i] = INF; dp[0] = 0;
		for( int i=0; i<=V2; i++ ) dp2[i] = INF; dp2[0] = 0;
		for( int i=0; i<n; i++ ) {
			int k = 1;
			while(k <= c[i]) {
				for( int j=V2; j>=w[i]*k; j-- ) dp[j] = min(dp[j], dp[j-w[i]*k]+k);
				c[i] -= k;
				k *= 2;
			}
			if(c[i]) {
				for( int j=V2; j>=w[i]*c[i]; j-- ) dp[j] = min(dp[j], dp[j-w[i]*c[i]]+c[i]);
			}
		}
		for( int i=0; i<n; i++ ) {
			for( int j=w[i]; j<=V2-V; j++ ) { // 最大的找钱数
				dp2[j] = min(dp2[j], dp2[j-w[i]]+1);
			}
		}
		int ans = INF;
		for( int i=0; i<=V2-V; i++ ) ans = min(ans, dp[V+i]+dp2[i]); // 枚举找钱数
		if(ans == INF) printf("-1\n");
		else printf("%d\n", ans);
	}
	return 0;
}

抱歉!评论已关闭.