贪心法是求解一类最优化问题的方法,它总是考虑在当前状态下局部最优(或较优) 的策略,来使全局的结果达到最优(或较优)。显然,如果采取较优而非最优的策略,得到的结果也无法是最优的。事实上,在现实应用中,最优策略往往可能不存在或者是很难被想到。但如果希望获得最优结果,则要求中间的每步策略都是最优的,因此严谨使用贪心法来求解最优化问题需要对采取的策略进行证明。 证明的一般思路是使用反证法及数学归纳法,即假设策略不能导致最优解,然后通过一系列的推导来得到矛盾,以此证明策略是最优的,最后再用数学归纳法保证全局最优。不过就平常使用来说,也许没有时间,或者不太容易对想到的策略进行严谨的证明(贪心的证明往往比贪
心本身更难)。
区间贪心
区间不相交问题:给出N个开区间(x, y),从中选择尽可能多的开区间,使得这些开区间两两没有交集。
首先考虑最简单的情况,如果开区间 I 1 I_1 I1被开区间 I 2 I_2 I2包含,如图a所示,那么显然 I 1 I_1 I1是更好的选择。因为 I 1 I_1 I1的跨度更小,与其他区间相交的概率也更小。
接下来把所有开区间按左端点x从大到小排序,左端点相同的按右端点从小到大排序,可以得到图b所示区间排序。实际题目中可能有更多的点,这里只举一个小例子。由于前面的排序选择,使得被包含的区间一定在包含它的区间的上方。如果去掉被包含的开区间,那么剩下的开区间一定满足 y 1 > y 2 > y 3 > y 4 y_1>y_2>y_3>y_4 y1>y2>y3>y4,如图b所示(提醒一下这里的y是右端点不是纵坐标)。现在考虑如何选取区间,通过观察 I 1 I_1 I1和 I 2 I_2 I2可以发现, I 1 I_1 I1左边是有有一部分被 I 2 I_2 I2给包含的,而右边是没有和其他任何区间有交集的。如果我们选择 I 1 I_1 I1,就可以使得被选取区间尽量往“右”扩展,这就增大了可选择空间的范围,也增大了可选择的数量。这道理和a是相同的。因此针对如图b所示这种情况,我们总是选择左端点最大的区间。
下面给出模板:
#include
#include
using namespace std;
const int maxn = 110;
struct Inteval
{
int x, y; // 开区间的左右端点
};
bool cmp(Inteval a, Inteval b)
{
if (a.x != b.x)
return a.x > b.x; // 先按左端点从大到小排序
else
return a.y < b.y; // 左端点相同的按右端点从小到大排序
}
int main()
{
int n;
while (cin >> n, n)
{
for (int i = 0; i < n; ++i)
cin >> I[i].x >> I[i].y;
sort(I, I + n, cmp); // 把区间排序
// ans记录不相交区间个数,lastX记录上一个被选中区间的左端点
int ans = 1, lastX = I[0].x;
for (int i = 1; i < n; ++i)
{
if (I[i].y <= lastX) // 如果该区间右端点在lastX左边
{
lastX = I[i].x; // 以I[i]作为新选中的区间
++ans;
}
}
cout << ans << endl;
}
return 0;
}
值得注意的是,如果将前面的讨论反过来,总是选择右端点最小的区间的策略也是可行的。 与上述代码的区别就在于cmp函数以及lastX记录上一个被选中区间的右端点。
与这个问题类似的是区间选点问题:给出N个闭区间[x, y],求最少需要确定多少个点,才能使每个闭区间中都至少存在一个点。 换句话说就是找到一个点集,使得所有区间中都能至少包含一个在点集中的点。比如对于闭区间[1, 4]、[2, 6]、[5, 7]、[6, 9]来说,最少需要两个点才能保证每个闭区间内都至少有一个点,比如3、7就能满足。当然这答案肯定是不唯一的,但是最少需要几个点是唯一的。
事实上,这个问题和区间不相交问题的策略是一致的。请看上图a,同样的,如果闭区间 I 1 I_1 I1被闭区间 I 2 I_2 I2包含,那么在 I 1 I_1 I1中取点肯定可以保证这个点一定在 I 2 I_2 I2内。然后同样的,把所有闭区间的左端点从大到小排序,去掉区间包含的情况,就能得到图b。显然,由于每个闭区间中都需要存在一个点,因此对左端点最大的区间 I 1 I_1 I1来说,取哪个点可以让它尽可能多地覆盖其他区间?很明显,只要取左端点就可以,这样这个点就能覆盖到尽可能多的区间。区间选点问题的代码只需要把区间不想交问题代码中的“I[i].y <= lastX”改为“I[i].y < lastX”即可。
我来解释一下为什么。在区间不相交问题中,由于区间都是开区间,假设对于开区间(x1, y1)和开区间(x2, y2)已经排好序,x1 > x2,但是y2 = x1。即使是这样,我们也要将闭区间(x2, y2)选进去,因为它们尽管有端点相等,但实际上是不相交的。在区间选点问题中,由于区间都是闭区间,而我们希望找到尽可能少的点覆盖尽可能多的区间,所以如果某两个闭区间的端点相等了,我们只需要取端点就能覆盖这两个闭区间。也就没必要在某一个区间中继续选点了,这就是为什么不能用等号的原因。
总的来说,贪心是用来解决一类最优化问题,并希望由局部最优策略来推得全局最优结果的算法思想。贪心算法适用的问题一定满足最优子结构性质,即一个问题的最优解可以由它的子问题的最优解有效地构造出来。
问题 A: 看电视
暑假到了,小明终于可以开心的看电视了。但是小明喜欢的节目太多了,他希望尽量多的看到完整的节目。
现在他把他喜欢的电视节目的转播时间表给你,你能帮他合理安排吗?
输入包含多组测试数据。每组输入的第一行是一个整数n(n<=100),表示小明喜欢的节目的总数。
接下来n行,每行输入两个整数si和ei(1<=i<=n),表示第i个节目的开始和结束时间,为了简化问题,每个时间都用一个正整数表示。
当n=0时,输入结束。
对于每组输入,输出能完整看到的电视节目的个数。
12
1 3
3 4
0 7
3 8
15 19
15 20
10 15
8 18
6 12
5 10
4 14
2 9
0
5
题目要求“完整看到的电视节目的个数”,所以这是一个区间不相交问题。
#include
#include
using namespace std;
struct Inteval
{
int x, y; // 开区间的左右端点
};
bool cmp(Inteval a, Inteval b)
{
if (a.x != b.x)
return a.x > b.x; // 先按左端点从大到小排序
else
return a.y < b.y; // 左端点相同的按右端点从小到大排序
}
int main()
{
int n;
while (cin >> n, n)
{
Inteval point[n];
for (int i = 0; i < n; ++i)
cin >> point[i].x >> point[i].y;
sort(point, point + n, cmp);
int ans = 1, lastSi = point[0].x;
for (int i = 1; i < n; ++i)
{
if (point[i].y <= lastSi)
{
lastSi = point[i].x;
++ans;
}
}
cout << ans << endl;
}
return 0;
}
问题 B: 出租车费
某市出租车计价规则如下:起步4公里10元,即使你的行程没超过4公里;接下来的4公里,每公里2元;之后每公里2.4元。行程的最后一段即使不到1公里,也当作1公里计费。
一个乘客可以根据行程公里数合理安排坐车方式来使自己的打车费最小。
例如,整个行程为16公里,乘客应该将行程分成长度相同的两部分,每部分花费18元,总共花费36元。如果坐出租车一次走完全程要花费37.2元。
现在给你整个行程的公里数,请你计算坐出租车的最小花费。
输入包含多组测试数据。每组输入一个正整数n(n<10000000),表示整个行程的公里数。
当n=0时,输入结束。
输出
对于每组输入,输出最小花费。如果需要的话,保留一位小数。
3
9
16
0
10
20.4
36
利用贪心的思想,肯定是希望将总路程分成多段路程,然后每一段路程求出最小花费的方案,最后就能得到整段路上的最小花费。假设整段路上的最小花费为P,此时应该这几种方案:
#include
#include
#include
using namespace std;
double cal(double n)
{
if (n <= 4)
return 10;
else if (n > 4 && n <= 8)
return 10 + (n - 4) * 2;
else
return 18 + (n - 8) * 2.4;
}
int main()
{
double n;
while (cin >> n, n)
{
double P = 0;
while (n > 12)
{
P += 18;
n -= 8;
}
P += cal(n); // 计算剩余的路程花费
if (P - int(P) != 0) // 减去自身的整数等于小数位,小数位不为0说明不是整数
cout << fixed << setprecision(1) << P << endl;
else // 转为整数输出
cout << (int)P << endl;
}
return 0;
}
问题 C: To Fill or Not to Fill
With highways available, driving a car from Hangzhou to any other city is easy. But since the tank capacity of a car is limited, we have to find gas stations on the way from time to time. Different gas station may give different price. You are asked to carefully design the cheapest route to go.
Each input file contains one test case. For each case, the first line contains 4 positive numbers: Cmax (<= 100), the maximum capacity of the tank; D (<=30000), the distance between Hangzhou and the destination city; Davg (<=20), the average distance per unit gas that the car can run; and N (<= 500), the total number of gas stations. Then N lines follow, each contains a pair of non-negative numbers: Pi, the unit gas price, and Di (<=D), the distance between this station and Hangzhou, for i=1,…N. All the numbers in a line are separated by a space.
For each test case, print the cheapest price in a line, accurate up to 2 decimal places. It is assumed that the tank is empty at the beginning. If it is impossible to reach the destination, print “The maximum travel distance = X” where X is the maximum possible distance the car can run, accurate up to 2 decimal places.
59 525 19 2
3.00 314
3.00 0
82.89
该题目所要解决的问题是:给定若干加油站信息,问能否驾驶汽车行驶一定的距离。如果能够行驶完全程,则计算最小花费。若不能行驶完全程,则最远能够行驶多长距离。
拿到这一题,首先判断汽车是否能够行驶到终点。什么情况下汽车无法行驶到终点呢?两种情况:起点根本就没有加油站,汽车无法启动;或者中途两个加油站之间的距离大于加满油后汽车能够行驶的最大距离。前者汽车行驶的最大距离为0.00,而后者最大距离为当前加油站的距离加上在这个加油站加满油后能够行驶的最大距离。在这里,需要将加油站按到杭州的距离从小到大排序。
接下来在能够行驶到终点的情况下计算最小花费。我们首先从路程来考虑,如果在路上,我们能够使用最便宜的汽油,当然就在那个加油站加油了。所以从起点开始遍历每个加油站。假设遍历到了第i个加油站,我们现在来判断在加油站i应该加多少油。设当前汽车离杭州的距离为curLen,当前加油站离杭州的距离为nodes[i].dis,加满油以后汽车能够行驶的最大距离为(dis=cmax*len)。这样就有node[i].dis <= curLen <= nodes[i].dis+dis,否则的话第i个加油站的油是不起作用的。于是在第i个加油站的作用范围内寻找有没有更为便宜的加油站,如果有,则下次使用这个加油站的油(j),这次汽车应该行驶到这个加油站,即touch=nodes[j].dis。如果没有找到更为便宜的加油站则可以在第i个加油站加满油,即touch=nodes[i].dis+dis。然后判断下次应该行驶到的距离与当前距离的关系,如果下次应当行驶到的距离大于当前距离,则汽车行驶,否则不动(也就是说上个加油站的油更便宜,后一个加油站也便宜,根本就不需要在当前加油站加油)。
问题 D: Repair the Wall
Long time ago , Kitty lived in a small village. The air was fresh and the scenery was very beautiful. The only thing that troubled her is the typhoon.
When the typhoon came, everything is terrible. It kept blowing and raining for a long time. And what made the situation worse was that all of Kitty’s walls were made of wood.
One day, Kitty found that there was a crack in the wall. The shape of the crack is
a rectangle with the size of 1×L (in inch). Luckly Kitty got N blocks and a saw(锯子) from her neighbors.
The shape of the blocks were rectangle too, and the width of all blocks were 1 inch. So, with the help of saw, Kitty could cut down some of the blocks(of course she could use it directly without cutting) and put them in the crack, and the wall may be repaired perfectly, without any gap.
Now, Kitty knew the size of each blocks, and wanted to use as fewer as possible of the blocks to repair the wall, could you help her ?
The problem contains many test cases, please process to the end of file( EOF ). For each test case , print an integer which represents the minimal number of blocks are needed. 2 2 1 题目的意思是有一个 1 × L 1\times L 1×L大的缺口,现在有N个砖块,每个砖块的长度在第二行给出。现在求最少需要多少个砖块就可以将缺口填满,砖块可以切割。 问题 E: FatMouse’s Trade FatMouse prepared M pounds of cat food, ready to trade with the cats guarding the warehouse containing his favorite food, JavaBean. The input consists of multiple test cases. Each test case begins with a line containing two non-negative integers M and N. Then N lines follow, each contains two non-negative integers J[i] and F[i] respectively. The last test case is followed by two -1’s. All integers are not greater than 1000. For each test case, print in a single line a real number accurate up to 3 decimal places, which is the maximum amount of JavaBeans that FatMouse can obtain. 4 2 2.286 题目的意思是杰瑞手上有M磅的猫食想拿去汤姆守卫的仓库换奶酪,仓库里有N个房间。这N个房间的特点是,第i个房间有J[i]磅奶酪,如果希望得到这J[i]磅的奶酪,杰瑞需要付出F[i]磅的猫食。但是他不必这么做,因为存在这么一个规矩,如果杰瑞仅仅支付 %a * F[i] 磅的猫食,也能能到 %a * J[i] 磅的奶酪。其中%a是一个实数。现在题目希望给出数据,让你求出杰瑞用这M磅猫食最多能得到多少磅奶酪。 问题 F: 迷瘴 小明正在玩游戏,他控制的角色正面临着幽谷的考验—— 输入数据的第一行是一个整数C,表示测试数据的组数; 对于每组测试数据,请输出一个整数和一个浮点数; 2 35 0.01 题目很简单,搞清楚数学原理就行了。同体积不同浓度的溶液混合后的浓度值,等于浓度和除以总体积。本题问的就是,在保证这个浓度值小于给定浓度值的情况下,所能调配出的最大体积。 这个位置我生生调试了将近一个小时,不能加’=’!!! 问题 G: 找零钱 小智去超市买东西,买了不超过一百块的东西。收银员想尽量用少的纸币来找钱。 有多组数据 1<=n<=99; 对于每种数量不为0的纸币,输出他们的面值*数量,再加起来输出 25 20 * 1+5 * 1 暴力,大力出奇迹。 一定要自己写一遍哦~~~
Each test case contains two lines.
In the first line, there are two integers L(0
In the second line, there are N positive integers. The ith integer Ai(0输出
If Kitty could not repair the wall, just print “impossible” instead.样例输入
12 11
14 3
27 11 4
109 5
38 15 6 21 32
5 3
1 1 1样例输出
1
5
impossibleNote
思路很明确,每次都找最长的砖块,如果长过缺口就直接填,没填满就继续寻找。所以我们需要对长度数组进行排序,排好序后,只需从0号元素往后取用砖块就可以。数组的下标恰好就能表示所用砖块数。要注意砖块和缺口的长度可以为 1 0 9 10^9 109,相加的话很容易超过int,所以要用long long来定义。#include
题目描述
The warehouse has N rooms. The i-th room contains J[i] pounds of JavaBeans and requires F[i] pounds of cat food. FatMouse does not have to trade for all the JavaBeans in the room, instead, he may get J[i]* a% pounds of JavaBeans if he pays F[i]* a% pounds of cat food. Here a is a real number. Now he is assigning this homework to you: tell him the maximum amount of JavaBeans he can obtain.输入
输出
样例输入
4 7
1 3
5 5
4 8
3 8
1 2
2 5
2 4
-1 -1样例输出
2.500Note
其实仔细观察数据就能发现,第i个房间奶酪:猫食是存在一个兑换比例的,而这个比例直接决定了猫食兑换奶酪的收益。当J[i]:F[i]越大时,意味着越少的只需要一点点的猫食就能得到很多奶酪。所以我们的基本思路是:
#include
题目描述
幽谷周围瘴气弥漫,静的可怕,隐约可见地上堆满了骷髅。由于此处长年不见天日,导致空气中布满了毒素,一旦吸入体内,便会全身溃烂而死。
幸好小明早有防备,提前备好了解药材料(各种浓度的万能药水)。现在只需按照配置成不同比例的浓度。
现已知小明随身携带有n种浓度的万能药水,体积V都相同,浓度则分别为Pi%。并且知道,针对当时幽谷的瘴气情况,只需选择部分或者全部的万能药水,然后配置出浓度不大于 W%的药水即可解毒。
现在的问题是:如何配置此药,能得到最大体积的当前可用的解药呢?
特别说明:由于幽谷内设备的限制,只允许把一种已有的药全部混入另一种之中(即:不能出现对一种药只取它的一部分这样的操作)。输入
每组测试数据包含2行,首先一行给出三个正整数n,V,W(1<=n,V,W<=100);
接着一行是n个整数,表示n种药水的浓度Pi%(1<=Pi<=100)。输出
其中整数表示解药的最大体积,浮点数表示解药的浓度(四舍五入保留2位小数);
如果不能配出满足要求的的解药,则请输出0 0.00。样例输入
1 35 68
1
2 79 25
59 63样例输出
0 0.00Note
基本思路就是将n瓶万能药水的浓度排个序,当然是浓度越小的越好,因为题目要求不大于W。然后每次加入一瓶新的药水,更新一次浓度值,然后与W比较大小。
这题太催吐了,就if ((double)(curP + P[maxV]) / ((maxV + 1) * 100) > ((double)W / 100))
#include
题目描述
纸币面额分为50 20 10 5 1 五种。请在知道要找多少钱n给小明的情况下,输出纸币数量最少的方案。 1<=n<=99;输入
输出
样例输入
32样例输出
20 * 1+10 * 1+1 * 2
(CSDN排版问题,’*’ 号两边是没有空格的)Note
#include