43-圣诞老人的礼物

从这里开始我们学习一个新的思想,贪心的思想,仍然是通过例子来学习思想。

问题描述

圣诞节来临了,圣诞老人准备分发糖果,现在有多箱不同的糖果,每箱糖果有自己的价值和重量,每箱糖果都可以拆分成任意散装组合带走。圣诞老人的驯鹿雪橇最多只能装下重量W的糖果。请问圣诞老人最多能带走多大价值的糖果。

输入:
第一行由两个部分组成,别为糖果箱数正整 n (1 <= n <= 100) ,驯鹿能承受的最大重量正整数 w (0 < w <10000),两个数用空格隔开。其余 n 行每行对应一箱糖果,由两部分组成,分别为一箱糖的价值正整数 v 和重量正整数 w,中间用空格隔开。
输出:
输出圣诞老人能带走的糖果最大总价值,保留 1 位小数 。输出为一行,以换行符结束。

样例输入
4 15
100 4
412 8
266 7
591 2
样例输出:
1193.0

问题分析

按礼物的价值/重量比从大到小依次选取礼物,对选取的礼物尽可能多地装,直到达总重量w。
先对礼物的价值重量比排序,再遍历一遍,所以复杂度为 O(nlogn)

#include<iostream>
#include<algorithm>
using namespace std;

const double eps = 1e-6;
struct Candy {
		int v, w;
		bool operator < (const Candy & c)
		{
			return double(v) / w - double(c.v) / c.w > eps;
		}
} candies[110];

int main()
{
	int n, w;
	cin >> n >> w;
	for (int i = 0; i < n; ++i)
		cin >> candies[i].v >> candies[i].w;
	sort(candies, candies + n);
	int totalW = 0;
	double totalV = 0;
	for (int i = 0; i < n; i++)
	{
		if (totalW + candies[i].w < w)
		{
			totalV += candies[i].v;
			totalW += candies[i].w;
		}
		else
		{
			totalV += (double)(w - totalW)*candies[i].v / candies[i].w;
			break;
		}
	}
	cout << totalV << endl;
	return 0;
}

证明

直观感觉上面的方法是正确的,现在给出证明。这里用替换法证明。上面我们的计算结果序列为 b1 , b2 , b3 ……,假设有另外一个序列 a1 , a2 , a3 ……,可以使得价值最大。如果我们可以证明两个序列相同,就得出上面贪心的思想是正确的。

将两个序列按价值 / 重量比从大到小排序后得:
序列 1:a1 , a2 , a3……
序列 2依次进行比较:
序列 2:b1 , b2 , b3

价值重量比相同的若干箱糖果,可以合并成一箱。所以两个序列中元素都不复。
将 ai 与 bi 进行对比对于发现的第一个 ai! = bi ,则必有: ai< b < bi,因为序列2是全部物品完全按照价值重量比排序,从大到小挑选的, bi 肯定是未挑选物品中价值重量比最大的,则在序列 1 中,用 bi 这种糖果替代若干重量的 ai 这种糖果,则会使得序列 1 的总价值增加,这和序列 1 是价值最大的取法矛盾。

序列 2 不可能是序列 1 的一个前缀。如果序列 2 为序列 1 的前缀,则序列 1 的总重量大于序列 2 的总重量,大于可以装载的总重量了。

所以:序列 1 = 序列 2

贪心算法

贪心算法的主要思想为:每一步行动总是按某种指标选取最优的操作来进行,该指标只看眼前,并不考虑以后可能造成的影响。 该指标只看眼前,并不考虑以后可能造成的影响。

贪心算法需要证明其正确性。

“圣诞老人礼物”题,若糖果只能整箱拿,则贪心法错误。
考虑下面例子:
3个箱子 (8,6) (5,5) (5,5),雪橇总容量10

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值