POJ 3040 Allowance 贪心

Description
As a reward for record milk production, Farmer John has decided to start paying Bessie the cow a small weekly allowance. FJ has a set of coins in N (1 <= N <= 20) different denominations, where each denomination of coin evenly divides the next-larger denomination (e.g., 1 cent coins, 5 cent coins, 10 cent coins, and 50 cent coins).Using the given set of coins, he would like to pay Bessie at least some given amount of money C (1 <= C <= 100,000,000) every week.Please help him ompute the maximum number of weeks he can pay Bessie.
Input
Line 1: Two space-separated integers: N and C
Lines 2…N+1: Each line corresponds to a denomination of coin and contains two integers: the value V (1 <= V <= 100,000,000) of the denomination, and the number of coins B (1 <= B <= 1,000,000) of this denomation in Farmer John’s possession.
Output
Line 1: A single integer that is the number of weeks Farmer John can pay Bessie at least C allowance
Sample Input
3 6
10 1
1 100
5 120
Sample Output
111
Hint
INPUT DETAILS:
FJ would like to pay Bessie 6 cents per week. He has 100 1-cent coins,120 5-cent coins, and 1 10-cent coin.
OUTPUT DETAILS:
FJ can overpay Bessie with the one 10-cent coin for 1 week, then pay Bessie two 5-cent coins for 10 weeks and then pay Bessie one 1-cent coin and one 5-cent coin for 100 weeks.

题目大意:
有n中硬币,每种硬币有对应的数量,且保证大面额一定是小面额的倍数,现在每周要付给工人至少c元,问最多能支付多少周

解题思路:
考虑一次支付的过程,大于c的硬币每个只能用于支付一周,关键在于面值小于c的硬币该如何分配。
首先我们应该要尽量选择面额大的,这样在数额接近c的时候,我们能有更多的小面额去组成更多的组合,从而使数额在满足条件的时候尽可能地接近c,即每次浪费最少的钱。
题目中还有关键的一点是保证大面额一定是小面额的倍数,这保证了小面额在组成比大面额大的数额时,一定会先组成大面额的数额,那么不妨用大面额去替换,原因刚才已经解释了。

解题步骤:
先用大面额的去组成c,但不要超过c,然后再用小面额去填补差额,如果小面额无法填补差额,则再使用一个大面额硬币中的小面额硬币去填补。注意上述过程不能直接模拟,否则会t,应该在求得一种支付方案后就尽可能地使用该方案,直到某一种硬币不能满足方案要求,然后就在新的硬币配置情况下求解新的方案。
具体实现见代码注释

AC代码

#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<math.h>
using namespace std;
#define INF 0x3fffffff
struct node {
	long long v, num;
	int t;//t代表方案中该硬币的使用数量
}list[21];
long long c, ans;
bool cmp(node a, node b) {//根据面额从大到小排序
	return a.v > b.v;
}
int main() {
	int n;
	while (cin >> n >> c) {
		ans = 0;
		for (int i = 1; i <= n; i++) {
			scanf("%lld%lld", &list[i].v, &list[i].num);
			//大于c的硬币每个只能用于支付一周
			if (list[i].v >= c) { ans += list[i].num; i--; n--; }
		}//list中value都是小于c的
		sort(list + 1, list + 1 + n, cmp);
		while (n > 0) {
			long long tmp = c;
			for (int i = 1; i <= n; i++) {//求出支付方案
				list[i].t = 0;
				while (tmp >= list[i].v) {
					if (list[i].t < list[i].num) {
						list[i].t++; tmp -= list[i].v;
					}
					else {
						break;
					}
				}
				if (i == n) {//已经考虑完所有的面额
					if (tmp > 0) {//如果差额还没被消除 即小面额无法填补差额
						int j;
						for (j = n; j > 0; j--) {//找到还有富余的最小面额
							if (list[j].t < list[j].num) {
								list[j].t++; tmp -= list[j].v;
								break;
							}
						}
						for (int k = n; k > j; k--) {//本方案中其余更小的面额就不再使用了
							list[k].t = 0;
						}
					}
				}
			}
			if (tmp > 0) { break; }//如果无力支付c,结束
			long long ans_tmp = INF;
			for (int i = 1; i <= n; i++) {
				if (list[i].t == 0) { continue; }
				ans_tmp = min(ans_tmp, list[i].num / list[i].t);//方案能够使用的最大次数
			}
			ans += ans_tmp;
			for (int i = 1; i <= n; i++) {
				list[i].num -= (list[i].t*ans_tmp);//各类硬币的剩余数量
			}
			while (n > 0 && list[n].num == 0) { n--; }//硬币数量为0则削减硬币种数
			for (int i = 1; i <= n; i++) {
				if (list[i].num == 0) {//硬币数量为0则削减硬币种数
					//将最后一个数量不为0的硬币提到该位置
					list[i].v = list[n].v; list[i].num = list[n].num; n--;
					while (n > 0 && list[n].num == 0) { n--; }//硬币数量为0则削减硬币种数
				}
			}
			sort(list + 1, list + 1 + n, cmp);//重新排序
		}
		cout << ans << endl;
	}
}

————————————————————————————————————
刚刚参考了一下大神们的博客,发现在求得支付方案时,可以先不管硬币的剩余数量,使用k=tmp/list[i].v; m=min(k,list[i].num); tmp-=m*list[i].v; list[i].t=m; 这样做的话就不用在硬币数量为0时削减硬币种数了,也不用将最后一个数量不为0的硬币向前提了,不容易出错哦( ̄︶ ̄)

然后我就发现,我的代码中也已经有了类似的判断list[i].t < list[i].num,所以本来就不用额外地削减硬币种类什么的。。。。。 汗-_-||
精简后的AC代码

#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<math.h>
using namespace std;
#define INF 0x3fffffff
struct node {
	long long v, num;
	int t;//t代表方案中该硬币的使用数量
}list[21];
long long c, ans;
bool cmp(node a, node b) {//根据面额从大到小排序
	return a.v > b.v;
}
int main() {
	int n;
	while (cin >> n >> c) {
		ans = 0;
		for (int i = 1; i <= n; i++) {
			scanf_s("%lld%lld", &list[i].v, &list[i].num);
			//大于c的硬币每个只能用于支付一周
			if (list[i].v >= c) { ans += list[i].num; i--; n--; }
		}//list中value都是小于c的
		sort(list + 1, list + 1 + n, cmp);
		while (true) {
			long long tmp = c;
			for (int i = 1; i <= n; i++) {//求出支付方案
				list[i].t = 0;
				while (tmp >= list[i].v) {
					if (list[i].t < list[i].num) {
						list[i].t++; tmp -= list[i].v;
					}
					else {
						break;
					}
				}
				if (i == n) {//已经考虑完所有的面额
					if (tmp > 0) {//如果差额还没被消除 即小面额无法填补差额
						int j;
						for (j = n; j > 0; j--) {//找到还有富余的最小面额
							if (list[j].t < list[j].num) {
								list[j].t++; tmp -= list[j].v;
								break;
							}
						}
						for (int k = n; k > j; k--) {//其余更小的面额就不再使用了
							list[k].t = 0;
						}
					}
				}
			}
			if (tmp > 0) { break; }//如果无力支付c,结束
			long long ans_tmp = INF;
			for (int i = 1; i <= n; i++) {
				if (list[i].t == 0) { continue; }
				ans_tmp = min(ans_tmp, list[i].num / list[i].t);//方案能够使用的最大次数
			}
			ans += ans_tmp;
			for (int i = 1; i <= n; i++) {
				list[i].num -= (list[i].t*ans_tmp);//各类硬币的剩余数量
			}
		}
		cout << ans << endl;
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值