题解 P1478 【陶陶摘苹果(升级版)】

· · 题解

【睿智萌新第一次写题解却因为重复原因审核都没过】

【留在这当黑历史丢人吧】

本辣鸡第一次发表题解大家就当看着玩吧qwq

.

(入门难度升级了还是很好写2333)

.

下面为奇妙の题解

.

看着上面的一群dalao都写的很详细了我就糊点奇怪的东西在这吧

.

首先是解题思路,这道题相对于之前那道入门难度的题目新加入了力气这个变量,要在剩余力气的范围内尽量摘到最多的苹果,首先想到的就是贪心吧。

(不知道有没有萌新不知道贪心算法是啥呢,那我顺便说一下吧,个人觉得这是最容易理解的算法,毕竟和生活实际很接近,比如说你有十块钱,有两个地方卖苹果,苹果的质量都是一模一样的,但家店卖三元一个,另一家卖五元一个,如果你想多吃几个苹果的话,当然会选择去价格便宜的那家店)

这题也是一样,要使得到的苹果最多,当然要每次都摘那个最省力的苹果,这样最后的结果一定是最优的

(再提一句,贪心并不能适用于所有的情况,如果前面的选择会影响后面的情况,那么贪心就不适用了,详情可见数字三角形(P1216) 。)

(PS:如果还是理解不了的话我再举个例子,比如说刚刚的买苹果,如果你一直买三块钱的苹果,导致卖五块钱的那家店破了产,然后三块钱的店趁机垄断市场把三块钱提高到十块钱,这时候你想吃苹果就只能以亏爆的价格购买,这种情况下两个店都买一些或者均衡着买才是最终的最佳情况)

好了,如果知道贪心算法是什么样的这题就很简单了。

说实话我第一眼看见这个题脑子里想到的直接是 贪心+sort+前缀和 一遍过(前缀和其实可以不用,直接一遍循环过来用时可能更少)

考虑到萌新可能不懂sort和前缀和,所以我还是慢慢讲吧(疯狂拖长度)

首先,使用贪心需要找到每次的最小值,这时候使用排序肯定是最优且最方便的

但是如果不会排序呢?放心,这个题不用排序直接暴力也能过

下面贴代码

#include<iostream>
#include<cstdio>
using namespace std;
#define MAXN 5005
#define MAX 999999999 
//新人注意,这里的define语句是很有用的哦
//可以避免对大数字的重复输入 
int n,high[MAXN],tired[MAXN],s,a,b,num;
//n为苹果的个数
//high为各苹果高度
//tired 为摘苹果需要的体力
//s,a,b为剩余体力,凳子高,伸手高度
//num为能摘到的果子数 
int main(){
    cin>>n>>s>>a>>b;
    //输入 
    a+=b;
    //注意这里,a,b在此题中不单独起作用,判定时是作为整体,所以可直接将b加到a上 
    for(int i=1;i<=n;i++){//输入各果子数据 
        scanf("%d%d",high+i,tired+i);
    }
    int min=MAX,minn;
    //每次找耗体力最少的苹果 
    //minn用来记录找到的苹果的标号 
    for(int i=1;i<=n;i++){
        min=MAX;minn=0;
        //查找之前先归零 
        for(int j=1;j<=n;j++){
            if(high[j]<=a&&tired[j]<min){
                min=tired[j];minn=j;
            }
            //依次查找,找到比min小的就更新min和minn 
        }
        if(s>=min){//把这几个MAX换为999999999后你就知道之前的define语句有什么作用了qwq 
            s-=min;num++;tired[minn]=MAX;
        }else break;
        //如果可以摘的话,就摘下来并记录它
        //(把tired设为最大值后不会再次记录此果子 
    }
    cout<<num<<endl;//正经の输出 
    return 0;//正经の结束 
}

注:这个程序涉及到重复找最小值的操作(各位应该都会吧不会我也懒得讲了qwq

虽然复杂度为O(n^2),但对于本题还是可以AC的(顺带还能练习多次找最小值)

接下来是我自己习惯的做法,直接sort+前缀和

sort的话...反正我已经中毒了,除了sort不会其他的排序了..

(我知道这样不能理解排序的精髓而且最坏情况会退化到O(N^2)但sort真的很好用啊代码又少还能自己带比较函数qwq)

(建议新人先把各个排序都理解清楚了再调用此函数)

PS:使用sort需调用algorithm头文件

前缀和其实也很好理解,就是把本来表示单个果子的疲劳值的数组变成了表示前i各果子的数组

对于本题可能没有什么作用,但是如果给出多个体力值s求最大能摘几个的话就可以大幅度优化复杂度,所以我建议不会使用的OIer把这种方法仔细地理解清楚

(仍然是例子,比如说本来排序后耗费体力值为 3 5 6 7,求前缀和后数组变为 3 8 14 21, 这样,只用比较s与21的大小就知道是否可以摘前四个果子,是不是比依次用s减去 3 5 6 7快很多呢?

AC代码如下

#include<iostream>
#include<algorithm>
using namespace std;
int n,s,a,b;
//分别为个数,体力,凳子高,身高 
int i=1,apple[5005];
//i用来记录读入的苹果,apple用来记录各苹果需要的体力 
int main(){
    cin>>n>>s>>a>>b;//普通の读入 
    a+=b;//上面讲过了哦 
    int high,tire;
    //读入时使用的变量 
    while(cin>>high>>tire){
        //某奇特的输入技巧,可直接判断是否输入完并输入值
        if(high==0)break;
        //测试时加入的,否则在控制台程序不知道你是否输入完了(但文件读写就不会出现这种情况)
        if(high<=a){
            apple[i]=tire;i++;
        }
        //可以采摘就记录并更改i的值准备记录下一个
    }
    //这里用到一个技巧,对于过高的苹果,只能看着发呆,
    //当前体力值和需要体力值都不能让它下来,所以直接跳过它开始下一个 
    sort(apple+1,apple+i);
    //排序毒品
    for(int j=2;j<=i;j++){
        apple[j]+=apple[j-1];
    }
    //计算前缀和(也可以不用 
    for(int j=1;j<=i;j++){
        if(s<apple[j]){
            cout<<j-1<<endl;return 0;
        }
    }//判定s与所有果子的大小 
    cout<<i-1<<endl;
    return 0;
} 

PS:虽然说不能过分依赖sort,但这种排序方法还是要掌握的,因为

①:头文件自带的函数基本上都是最佳优化的,可以节省很多时间

②:考试时时间是很紧的,花时间手写一个排序算法不如直接写一个sort+自定义函数

③:sort功能十分强大,不仅可以对任何类型排序,还可以自定义排序的方法,作用极大

(关于sort怎么用我就不多说了,百度上都有很详细的讲解,记住是左闭右开的就行

.

.

~~是不是已经觉得很多了? 其实我下面还有两种解法哈哈哈哈哈没想到吧~~

.

.

通过刚才的sort排序,我们可以很快地把答案求出来。

但对于这种简单(对于有一定算法基础的人来说)题,不能只满足于把这个题目写出来

大家想一想,使用sort的确一遍过了,但是输出了后,你知道他到底摘了哪几个果子吗?

如果题目让我们输出具体摘到的是哪几个果子,这时候怎么办呢?

使用一开始的非排序算法的确可以,但太慢了不是吗?

如果使用sort,顺序又会乱,无法记录

这时候,就要使用结构体来记录多组数据了

Warning:新人不必急着把下面的两个解法完全理解透,毕竟这样玩已经有超过普及-难度的意思了

(但也可以先看看嘛毕竟文字还是很多的qwq)

代码如下:

注:加入了某些奇♂怪♂的东西

//涛涛摘苹果升级版 使用结构体加sort
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int appnum,a,b,power;
//分别为 苹果数,凳子高,身高,体力 
struct apple0{
    int length,tired;
    int rank;
} apple[5005];
 //定义一个结构体,
 //length和tired记录高度和疲劳值 
 //使用rank来记录第几个 
bool cmp(apple0 a,apple0 b){
    if(a.tired<b.tired)return 1;
    return 0;
}
//自定义比较函数, 按照疲劳值升序排列 
int main(){
    cin>>appnum>>power>>a>>b;//简单の输入 
    a+=b;//见之前代码 
    int len,tir;
    for(int i=1;i<=appnum;i++){
        scanf("%d%d",&len,&tir);
        apple[i].length=len;apple[i].tired=tir;apple[i].rank=i;
    }
    //输入并记录各个数值 
    sort(apple+1,apple+appnum+1,cmp);
    int cnt=0;
    cout<<"摘到了哪几个果子呢?"<<endl;
    for(int i=1;i<=appnum;i++){
        if(power>=apple[i].tired&&a>=apple[i].length){
            power-=apple[i].tired;
            cnt++;cout<<apple[i].rank<<' ';
        }
        if(power<apple[i].tired&&a>=apple[i].length)break;//注意:在排序的背景下,这里加不加后半段都无所谓,反正后面的你都摘不到了,是吗? 
    }
    //未使用前缀和,但通过rank的值输出了是哪几个 
    cout<<endl;
    cout<<"涛涛这傻孩子摘到的果子数:"<<endl<<cnt;
    return 0;
}

好了,说道这里也差不多结束了

其实这个题用桶排也是可以的,因为看见有dalao写过所以我就不多写了

(我会跟你说是因为我写了四个代码后实在懒得再写了吗)

那下面就是我要说的最后一个方法,使用动态规划的思路,用记忆化解01背包的方法写这个题

因为可以把每个苹果的价值看为1,需要空间为疲劳值,所以01背包的方法是完全适用的

(虽然说有杀鸡用牛刀的嫌疑但是用来练习01背包还是可以的~)

直接扔代码吧

//涛涛摘苹果升级版 -- 蛇皮解法:01背包
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std; 
int apple[5005];
int n,power;
int i=1;
int ed[5005][1005];
//记忆化使用,保证每个点只算一遍 
int maxn;
int dfs(int step,int power){ 
    if(step==i)return ed[step][power]=0;//同记忆化 
    if(ed[step][power]>=0)return ed[step][power];
    if(power>=apple[step])return ed[step][power]=max(dfs(step+1,power-apple[step])+1,dfs(step+1,power));//还能摘就继续搜索 
    else return ed[step][power]=dfs(step+1,power);
}

int main(){
    int a,b;
    cin>>n>>power>>a>>b;
    a+=b;
    int high=0,tir=0;
    while(cin>>high>>tir){//读入各个苹果(对于太高的苹果直接忽略就是了) 
        if(high<=a){
            apple[i]=tir;i+=1; 
        }else continue;
    }
    memset(ed,-1,sizeof(ed));
    cout<<dfs(1,power);//直接输出结果 
} 

好了,到此四种解法已全部展示完毕

手打不易,如果你觉得这篇题解对你有意义的话,点个赞或者评论支持一下都可以哦~

如果有不足的地方,也欢迎dalao指正,比较我是第一次写题解,而且本咸鱼学算法的时间也不超过两星期,可能错误会比较多呢....qwq

感谢各位~