POJ 1252 Euro Efficiency(完全背包, 找零问题, 二次DP)

Description

On January 1st 2002, The Netherlands, and several other European countries abandoned their national currency in favour of the Euro. This changed the ease of paying, and not just internationally. 
A student buying a 68 guilder book before January 1st could pay for the book with one 50 guilder banknote and two 10 guilder banknotes, receiving two guilders in change. In short:50+10+10-1-1=68. Other ways of paying were: 50+25-5-1-1, or 100-25-5-1-1.Either way, there are always 5 units (banknotes or coins) involved in the payment process, and it 
could not be done with less than 5 units. 
Buying a 68 Euro book is easier these days: 50+20-2 = 68, so only 3 units are involved.This is no coincidence; in many other cases paying with euros is more efficient than paying with guilders. On average the Euro is more efficient. This has nothing to do, of course, with the value of the Euro, but with the units chosen. The units for guilders used to be: 1, 2.5, 5, 10, 25, 50,whereas the units for the Euro are: 1, 2, 5, 10, 20, 50. 
For this problem we restrict ourselves to amounts up to 100 cents. The Euro has coins with values 1, 2, 5, 10, 20, 50 eurocents. In paying an arbitrary amount in the range [1, 100] eurocents, on average 2.96 coins are involved, either as payment or as change. The Euro series is not optimal in this sense. With coins 1, 24, 34, 39, 46, 50 an amount of 68 cents can be paid using two coins.The average number of coins involved in paying an amount in the range [1, 100] is 2.52. 
Calculations with the latter series are more complex, however. That is, mental calculations.These calculations could easily be programmed in any mobile phone, which nearly everybody carries around nowadays. Preparing for the future, a committee of the European Central Bank is studying the efficiency of series of coins, to find the most efficient series for amounts up to 100 eurocents. They need your help. 
Write a program that, given a series of coins, calculates the average and maximum number of coins needed to pay any amount up to and including 100 cents. You may assume that both parties involved have sufficient numbers of any coin at their disposal. 

Input

The first line of the input contains the number of test cases. Each test case is described by 6 different positive integers on a single line: the values of the coins, in ascending order. The first number is always 1. The last number is less than 100. 

Output

For each test case the output is a single line containing first the average and then the maximum number of coins involved in paying an amount in the range [1, 100]. These values are separated by a space. As in the example, the average should always contain two digits behind the decimal point. The maximum is always an integer. 

Sample Input

3
1 2 5 10 20 50
1 24 34 39 46 50
1 2 3 7 19 72

Sample Output

2.96 5
2.52 3
2.80 4

Q : 如何处理找零

A : dp[v] = min{dp[v-w[i]]+1, dp[v+w[i]]+1}. 针对此题(尚不了解方法是否有普适性), 可以先求一步不找零情况下的完全背包问题, 然后再进行一步完全背包, 比较哪种方案用的个数较少

 

思路:

1. 令 dp[v] 表示拼出总面额为 v 的钱所需要的最少硬币数

2. 求解一步完全背包, 这一步假设不允许找零, dp[v] = min(dp[v], dp[v-w[i]]+1)

3. 然后由状态转移方程, 求解找零的最优解, dp[v] = min(dp[v], dp[v+w[i]+1)

 

总结:

1. 完全背包问题, 当 dp[i] = f(dp[i+xxx]) 时, 第二层循环是从大到小遍历, 因为完全背包要求等式右边是同行的数据(假如使用二维矩阵存储dp[i][j]的话)

2. 找零问题会使总的钱数超过目标值, 比如为了得到 goal(100), 可能给对方150, 对方找回 50, 因此 dp 数组需要开大一点

3. 第二步完全背包, 状态转移方程 dp[v] = min(dp[v], dp[v-w[i]+1), 为了方便理解还是先转化为二维空间存储最优解

dp[i][v] = min(dp[i-1][v], dp[i][v-w[i]]+1)

dp[i-1][v] 表示前 i-1 个硬币可以当做零钱找回的情况下的最优解, 当考虑到第 i 个硬币时, 假如把其当做了零钱, 那么若满足 dp[i][v+w[i]]+1 > dp[i-1][v], 则说明先把钱累积到 v+w[i], 然后再找一次零钱 w[i] 要比直接累积到 v 使用的硬币数少(说法不严谨, 但大致是这个意思)

逆着分析, 是有些难以理解

 

代码:

#include <iostream>
using namespace std;
const int MAXN = 110;
const int MAXM = 100*20;
const int n = 6;
int money[MAXN];
int dp[MAXM+10];
/*
 *	不允许找钱, 前 i 个 coin 相加恰好为 x money 
 *	最大的money 总额可能会超过 100
 *	完全背包, 每件物品可以放任意多次, 第二层循环(j) 从小到大遍历
 */
void withoutChange() {
	memset(dp, 0x3f, sizeof(dp));
	dp[0] = 0;	// "恰好"
	for(int i = 0; i < n; i++) {	
		for(int j = money[i]; j <= MAXM; j ++) {
			dp[j] = min(dp[j], dp[j-money[i]]+1);
		}
	}
}
/*
 *	允许找钱
 *	对 i, 枚举找回的钱
 *	允许找钱, 同时意味着一方给的钱可能会超过 100, 题目已经说明, 每组数据都包含1, 所以, 数据若是比较苛刻的话, maxn 得设置为 99 * 50
 *	状态转移方程的方向变了, 同时完全背包要求等式右边是新的数据, 因此, j 的遍历顺序为从大到小 
 *	dp[v] = min(dp[v], dp[v+w[i]]) 对于第 i 个硬币, 要么作为零钱, 要么不作为, 反过来, 好难想
 */
void withChange() {		
	for(int i = 0; i < n; i ++) {
		for(int j = MAXM-money[i]; j >= 1; j --) {
			dp[j] = min(dp[j], dp[j+money[i]]+1);
		}
	}
}

int main() {
	freopen("E:\\Copy\\ACM\\测试用例\\in.txt", "r", stdin);
	int testCase;
	cin >> testCase;
	while(testCase--) {
		for(int i = 0; i < n; i ++) {
			scanf("%d", &money[i]);
		}
		float avg_coin = 0.0;
		int max_coin = 0;
		// first step
		withoutChange();
		// second step
		withChange();
		for(int i = 1; i <= 100; i ++) {
			avg_coin += dp[i];
			max_coin = max(max_coin, dp[i]);
		}
		avg_coin = avg_coin/100;
		printf("%0.2f %d\n", avg_coin, max_coin);
	}
	return 0;
}

  

posted @ 2013-12-04 10:53  SangS  阅读(611)  评论(0编辑  收藏  举报