我们可以扮演一个贪心的人,在金子、银、铁中选择装入背包带走的话,作为一个贪心的人,肯定要把价值最大化,优先要选择装载价值较高的金子。
目录
什么是贪心算法
贪心算法(greedy algorithm),是用计算机来模拟一个「贪心」的人做出决策的过程。这个人十分贪婪,每一步行动总是按某种指标选取最优的操作。而且他目光短浅,总是只看眼前,并不考虑以后可能造成的影响。
可想而知,并不是所有的时候贪心法都能获得最优解,所以一般使用贪心法的时候,都要确保自己能证明其正确性。
证明方法
贪心算法有两种证明方法:反证法和归纳法。一般情况下,一道题只会用到其中的一种方法来证明。
反证法:如果交换方案中任意两个元素/相邻的两个元素后,答案不会变得更好,那么可以推定目前的解已经是最优解了。
归纳法:先算得出边界情况(例如n = 1)的最优解 F1,然后再证明:对于每个n,Fn+1都可以由 Fn推导出结果。
常见题型
最常见的贪心有两种。
我们将 XXX 按照某某顺序排序,然后按某种顺序(例如从小到大)选择。
我们每次都取 XXX 中最大/小的东西,并更新 XXX。(有时【XXX 中最大/小的东西】可以优化,比如用优先队列维护)
二者的区别在于一种是离线的,先处理后选择;一种是在线的,边处理边选择。
常见题型解法
排序解法:用排序法常见的情况是输入一个包含几个(一般一到两个)权值的数组,通过排序然后遍历模拟计算的方法求出最优值。
后悔解法:思路是无论当前的选项是否最优都接受,然后进行比较,如果选择之后不是最优了,则反悔,舍弃掉这个选项;否则,正式接受。如此往复。
训练:小木船过河
学校组织秋游的时候,小知和他的小伙伴准备乘坐小木船过河。
一只小木船最多只能乘坐两个人,且乘客的总重量不能超过小木船的最大承载量。为了尽量减少过河所需费用,所以小知希望找出可以让所有人过河的最少的小木船数量。现在给出小木船的最大承载量、过河总人数和每个人的重量。请你帮助小知计算出能让所有人过河的最少的小木船数量,并输出结果。
【输入描述】第一行有两个整数w,n,80<=w<=200,1<=n<=300,w为一条独木舟的最大承载量,n为人数;
接下来的一组数据为每个人的重量(不能大于船的承载量);
【输出描述】需要的最少独木舟的条数。
【输入样例1】85 6
5 84 85 80 84 83
【输出样例1】5
【输入样例2】100 5
50 50 90 40 60
【输出样例2】3
解题思路
为了能用最少的船帮助所有人过河,所以每次尽量运送最多的人是最好的。但是我们发现比较重的人有可能每次只能自己一个人过河,再加一个人重量就超过了最大载重。
于是我们就考虑到最佳的方案如下→
- 对所有乘客体重升序(降序也可以)排序。
- 因为一条独木舟最多只能乘坐两个人,且乘客的总重量不能超过独木舟的最大承载量,所以先将升序的体重首尾(最轻和最重的)相加,如果不超过最大载重,两人坐一艘船,即最优解,那么下一组比较次轻的和次重的。但是如果超过最大载重,则最重的那人坐一艘船,将最轻的和次重的重复步骤2。
- 别忘记最后,当次轻和次重是同一人时,他一人坐一艘船。
参考代码
#include<iostream>
#include<algorithm>
using namespace std;
bool cmp(int a,int b){
return a>b;
}
int main(){
int n,a[350],t=0,w;
cin>>w>>n;
int j=n;
for(int i=1;i<=n;i++)
cin>>a[i];
sort(a+1,a+1+n,cmp);
int i=1;
while(i<=j){
if(a[i]+a[j]>w)
{
t++;
i++;
}
else if(a[i]+a[j]<=w){
t++;i++;j--;
}
}
cout<<t;
return 0;
}
训练:救援物资
小知开着他的卡车准备给灾区送一些救援物资,他的卡车最大载重量为M公斤,同时他购买了N种食品,有食盐,白糖,大米等。已知第 i 种食品的拥有Wi公斤,其商品价值为Vi元/公斤,编程确定一个装货方案,使得装入卡车中的所有物品总价值最大。
【输入描述】第一行为卡车最大载重m公斤和n种食品;接下来每行表示第i种食品的重量Wi和价值Vi
【输出描述】输出总价值,结果保留两位小数
【输入样例】
10
3
3 4
4 5
2 6
【输出样例】44.00
解题思路
为了使装入卡车的货物价值最大,那么每次选取的必然是价值最大的货物。因此我们可以将每种货物的价值按照从大到小的顺序排序,然后每次依次选取价值大的货物。
在这之前需要判断重量是否超过承重,如果超过了,装满承重重量并结束,否则将货物全部装入,并且将车子承重减去已装入的重量,重复以上步骤直至结束。
参考程序
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
struct s{
double w,v;//重量,价值
}a[105];
bool cmp(s p,s q){
return p.v>q.v;
}
int main(){
int m,n;
cin>>m>>n;
for(int i=1;i<=n;i++)
cin>>a[i].w>>a[i].v;
sort(a+1,a+n+1,cmp);
double sum=0;
for(int i=1;i<=n;i++){
if(a[i].w<=m){
sum+=a[i].w*a[i].v;
m-=a[i].w;
}
else{
sum+=m*a[i].v;
break;
}
}
printf("%.2f\n",sum);
return 0;
}
训练:会场安排
ZY公司每天都有很多的会议要开,但是大会议室只有一个,如果有一个会议是在8点至9点开,则这个时间段不能安排别的会议,现在有一张会议安排计划的时间表,小知作为排班人员希望大会议室能够安排更多的会议,在保证更多会议的前提下,占用更少的时间以便会议室准备,请你来帮忙解决这个问题。
【输入描述】第一行输入n个会议(1<=n<=100),之后的每行分别记录这n个会议的开始和结束时间(整点,时间在0~24之间)。
【输出描述】第一行输出安排好之后的第一个会议的开始和结束时间;
第二行输出这间大会议室最多安排几个会议?
【输入样例】4
8 10
9 12
8 9
10 12
【输出样例】8 9
2
解题思路
解决这道题的思路是,我们先安排一个会议,再在安排了这个会议的前提下,在剩下的时间里尽可能多地安排其它会议。
这里我们不妨先安排整个会议室里的第一个会议,然后再在这个会议结束之后剩余的时间里安排尽可能多的会议。
这里正确的安排的方法是:
我们在安排完一个会议之后,要能使得剩下的时间尽可能多,这样就可以安排更多的会议。
所以我们每一次首先安排的会议是结束时间最早的那一个会议。因为结束时间最早的那个会议被先安排的话剩余时间是最多的。
参考代码
#include<iostream>
#include<algorithm>
using namespace std;
struct s{
int st,ed;
}a[110];
bool cmp(s b,s c)
{
if(b.ed!=c.ed)
return b.ed<c.ed;
return b.st>c.st;
}
int main()
{
int n,t=0,j=0;
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i].st>>a[i].ed;
sort(a+1,a+1+n,cmp);
for(int i=1;i<=n;i++)
{
if(a[i].st>=j)
{
j=a[i].ed;
t++;
}
}
cout<<a[1].st<<" "<<a[1].ed<<endl<<t;
return 0;
}