从这里开始我们学习一个新的思想,贪心的思想,仍然是通过例子来学习思想。
问题描述
圣诞节来临了,圣诞老人准备分发糖果,现在有多箱不同的糖果,每箱糖果有自己的价值和重量,每箱糖果都可以拆分成任意散装组合带走。圣诞老人的驯鹿雪橇最多只能装下重量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