(一般)POJ-3040 贪心,硬币问题

题目大意:夫约翰要给奶牛Bessie发工资了(你们结婚吧,生个牛头人( ̄_ ̄|||) ),每周至少 C 元。约翰手头上有面值V_i的硬币B_i个,这些硬币的最小公约数为硬币的最小面值。求最多能发几周?


题目链接:点击打开链接


分析:此贪心解释如下: 
1:大于c的就直接取; 
2:如果小于就从大到小拿钱,能拿多少拿多少,但不能超过c; 
3:如果2拿的钱小于c,就从小到大拿钱,能拿多少拿多少,但要求是超过c的时候是最小的,也就是说,这些钱的总和是大于c的最小数; 
4:将此类取钱方式求出次数,加到count中,然后返回第二步。

注意到可以这样采取贪心的前提: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),意思是将硬币按价值从小到大排序后,满足第i+1个为第i个的倍数。具体为什么我也搞不太清,只能猜到涉及到了数论方面的知识,记住吧。。


附上代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
#define v first
#define b second
using namespace std;
typedef pair<int, int> node;
bool cmp(node num1, node num2){ return num1.v > num2.v; }
int main()
{
	int n, c;
	node val[30];
	scanf("%d%d", &n, &c);
	for (int i = 0; i < n; ++i)
		scanf("%d%d", &val[i].v, &val[i].b);
	sort(val, val + n, cmp);         //从大到小排序
	int i = 0;
	int count = 0;
	for (; i < n; ++i)            //完全大于c的就直接用
		if (val[i].v >= c)
		{
			count += val[i].b;
			val[i].b = 0;
		}
		else break;
	int need[30] = { 0 };        //记录每次你用了多少种coin
	while (1)
	{
		int sum = c;
		memset(need, 0, sizeof need);
		for (int j = i; j < n; ++j)         //先从大到小取,但不要超过c
			if (val[j].b && sum > 0)
			{
				need[j] += min(val[j].b, sum / val[j].v);           //取最小的个数,因为不超过,所以向下进位
				sum -= need[j] * val[j].v;       //减去已用了的
			}
		if (sum > 0)
			for (int j = n - 1; j >= i; --j)       //如果上面那次取法不够,就从小往大找补替,这样保证是最小损失
				if (val[j].b && sum > 0)
				{
					int mi;
					mi = min(val[j].b - need[j], (sum + val[j].v - 1) / val[j].v);    //取最少的,因为要超过或达到,所以向上进位
					if (mi > 0)         //这儿有可能是负数
					{
						sum -= mi * val[j].v;
						need[j] += mi;
					}
				}
		if (sum > 0) break;          //如果已经不够了,退出循环
		int x = 1e9 + 1;
		for (int j = i; j < n; ++j)                     //算是一种剪枝,将重复情况一次做完
			if (need[j]) x = min(val[j].b / need[j], x);  //取最小的每次需要的种类的次数
		count += x;
		for (int j = i; j < n; ++j)
			if (need[j]) val[j].b -= x * need[j];    //将用过的删除
	}
	printf("%d\n", count);
	return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值