今天,我们将从一道题引入贪心算法的认识.
题目
题目描述
又是一年秋季时,陶陶家的苹果树结了 n 个果子。陶陶又跑去摘苹果,这次他有一个 a 公分的椅子。当他手够不着时,他会站到椅子上再试试。
这次与 NOIp2005 普及组第一题不同的是:陶陶之前搬凳子,力气只剩下 s 了。当然,每次摘苹果时都要用一定的力气。陶陶想知道在s<0之前最多能摘到多少个苹果。
现在已知 n个苹果到达地上的高度 ,椅子的高度 a,陶陶手伸直的最大长度 b,陶陶所剩的力气 s,陶陶摘一个苹果需要的力气 ,求陶陶最多能摘到多少个苹果。
输入格式
第 1 行:两个数 苹果数 n,力气 s。
第 2 行:两个数 椅子的高度 a,陶陶手伸直的最大长度 b。
第 3 行~第 3+n−1 行:每行两个数 苹果高度 ,摘这个苹果需要的力气。
输出格式
只有一个整数,表示陶陶最多能摘到的苹果数。
输入输出样例
输入:
8 15 20 130 120 3 150 2 110 7 180 1 50 8 200 0 140 3 120 2输出:
4
要想知道详细题目,详见陶陶摘苹果(升级版) - 洛谷
梳理题目:
我们现在先重新梳理一下题目中数据:
的n表示着树上苹果的总和数,s表示陶陶搬凳子时候还剩下的力气.
a表示凳子的高度,b表示陶陶伸出手臂的最大长度.
接下来的表示每一个苹果的高度,表示陶陶摘每一个苹果所花的力气.
代码思路:
首先由于要输入每一个苹果的高度和陶陶摘这个苹果所要消耗的力气,我打算用一个结构体来储存数据:
struct apple{
int a_long,s_long;
}a[MAX],b[MAX];
在这个结构体中,a_long是每一个苹果距离地面的高度,s_long表示摘每一个苹果要花的力气.(怪鄙人英语不好).接着,a数组用来输入,b数组用来储存当陶陶伸出手臂的最大值时能摘到苹果.
接下来,我定义了一下的变量
int n,s,chair,arm,sum=0,j=1,cnt=0, a_long, s_long;
简单说明一下:
n,s,chair,arm,这四个变量前面的说了的,就不必详细的赘述了.
sum用来统计陶陶消耗的力量值,j用来计算有多少个苹果刚好能被陶陶摘到.
cnt用来计数最终在力量消耗完之前所能摘到的苹果个数.
a_long和s_long在冒泡排序里边做为一个交换的媒介.
接下来的输入就不用多说了.
cin>>n>>s>>chair>>arm;
for(int i=1;i<=n;i++){
cin>>a[i].a_long>>a[i].s_long;
}
接下来我们要进行关键的一步––筛选.
我们要把陶陶能够摘到的苹果储存在b数组里.前提是这个苹果的高度要小于等于板凳的高度加上陶陶伸长手臂能达到的最大值
接着就有了一下的判断
if(a[i].a_long<=chair+arm){
b[j].a_long=a[i].a_long;
b[j].s_long=a[i].s_long;
}
别急,还没完,外面还有一层循环:
for(int i=1;i<=n;i++){
if(a[i].a_long<=chair+arm){
b[j].a_long=a[i].a_long;
b[j].s_long=a[i].s_long;
j++;
}
}
接下来的一步,十分重要.
我们要把已经筛选出来的数据,重新排一下序.用什么顺序排序呢?当然是以每个苹果消耗力量的值以升序排序,更利于我们下一步的计算.
在这里,我用的是冒泡排序(sort当时突然一时想不起来了)
下面是冒泡排序里面的交换程序,要想知道冒泡排序的详细解法,详见排序算法(冒泡,桶排序,选择,sort)_cyy_yyds(蒟蒻练习生)的博客-CSDN博客
a_long=b[k].a_long;
s_long=b[k].s_long;
b[k].a_long=b[k+1].a_long;
b[k].s_long=b[k+1].s_long;
b[k+1].a_long=a_long;
b[k+1].s_long=s_long;
当然,这里面也可以用swap函数
接下来亮出整个冒泡的代码
for(int i=1;i<n;i++){
for(int k=1;k<j-i+1;k++){
if(b[k].s_long>b[k+1].s_long){
//swap(b[k].s_long,b[k+1].s_long);
//swap(b[k].a_long,b[k+1].a_long);
a_long=b[k].a_long;
s_long=b[k].s_long;
b[k].a_long=b[k+1].a_long;
b[k].s_long=b[k+1].s_long;
b[k+1].a_long=a_long;
b[k+1].s_long=s_long;
}
}
}
重头戏来了,来到了本讲最重要的地方––贪心算法.
要学会写代码,先要知道贪心是什么:
贪心算法是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,算法得到的是在某种意义上的局部最优解(源自于百度百科)
明白了贪心的含义,接下来我们来梳理一下本题如何使用贪心算法
首先,你要满足sum(目前消耗掉的力量)<s(原有的力量)时,才能进入循环,就会避免当力气达到了上线但陶陶继续摘苹果的奇葩.
进入循环后,cnt++,表示摘了一个苹果,然后用sum加上摘这个苹果所消耗的力量值.随后下标+1,可以得到以下代码
while(sum<s){
cnt++;
sum+=b[j].s_long;
j++;
}
可是接下来我们运行后会发现,加入树上的苹果已经摘完了,可是所消耗的力量值尚未达到上限时,它还会继续循环,并不会退出循环
所以我们应该加上一个条件
while((sum<s)&&(cnt<j1)){
cnt++;
sum+=b[j].s_long;
j++;
}
接下来输出
if(sum>s) cout<<cnt-1<<endl;//减去最后一次多统计的一次
else cout<<cnt<<endl;
本道题目讲解完毕,奉上最终代码(注:有些注释是我用来测试的,这道题我测试了好就才AC)
#include<iostream>
#define MAX 0xffff//65535
using namespace std;
struct node{
int a_long,s_long;
}a[MAX],b[MAX];
//结构体,b数组储存的是苹果的高度正好在淘淘的手臂长和凳子高之内的数
int n,s,chair,arm,sum=0,j=1,cnt=0, a_long, s_long;
//chair是凳子的高度,arm是手臂的长度
//sum用来统计力气是否用完,cnt用来计数能摘到多少果子
int main(){
cin>>n>>s>>chair>>arm;
for(int i=1;i<=n;i++)
cin>>a[i].a_long>>a[i].s_long;
//输入结构体a_long表示高度,s_long表示力气
for(int i=1;i<=n;i++){
if(a[i].a_long<=chair+arm){
b[j].a_long=a[i].a_long;
b[j].s_long=a[i].s_long;
j++;
}
}//用来统计恰好能摸到的果子,储存在b数组里
//cout<<"b data:\n";
//for(int i=1;i<j;i++){
// cout<<b[i].a_long<<" "<<b[i].s_long<<" ";
//}
//cout<<endl;
j--;
int j1=j;
for(int i=1;i<n;i++){
for(int k=1;k<j-i+1;k++){
if(b[k].s_long>b[k+1].s_long){
//swap(b[k].s_long,b[k+1].s_long);
//swap(b[k].a_long,b[k+1].a_long);
a_long=b[k].a_long;
s_long=b[k].s_long;
b[k].a_long=b[k+1].a_long;
b[k].s_long=b[k+1].s_long;
b[k+1].a_long=a_long;
b[k+1].s_long=s_long;
}
}
}//冒泡排序,排的是每一个果子所需要的力气,升序
//cout<<"after swap:\n";
//for(int i=1;i<=j;i++){
//cout<<b[i].a_long<<" "<<b[i].s_long<<" ";
//}
//cout<<"\n";
j=1;
//<<"power:"<<s<<"\n";
//cout<<"sum:"<<sum<<"\n";
//cout<<"cnt:"<<cnt<<"\n";
while((sum<s)&&(cnt<j1)){
cnt++;
sum+=b[j].s_long;
//cout<<"sum:"<<sum<<" ";
//cout<<"cnt:"<<cnt<<" ";
j++;
}//重头戏,贪心
if(sum>s) cout<<cnt-1<<endl;//减去最后一次多统计的一次
else cout<<cnt<<endl;
return 0;
}