蓝桥杯 旅行家的预算 By Assassin [复杂的贪心]

问题描述
  一个旅行家想驾驶汽车以最少的费用从一个城市到另一个城市(假设出发时油箱是空的)。给定两个城市之间的距离D1、汽车油箱的容量C(以升为单位)、每升汽油能行驶的距离D2、出发点每升汽油价格P和沿途油站数N(N可以为零),油站i离出发点的距离Di、每升汽油价格Pi(i=1,2,……N)。计算结果四舍五入至小数点后两位。如果无法到达目的地,则输出“No Solution”。
输入格式
  第一行为4个实数D1、C、D2、P与一个非负整数N;
  接下来N行,每行两个实数Di、Pi。
输出格式
  如果可以到达目的地,输出一个实数(四舍五入至小数点后两位),表示最小费用;否则输出“No Solution”(不含引号)。
样例输入
275.6 11.9 27.4 2.8 2
102.0 2.9
220.0 2.2
样例输出
26.95

思路:还是要先吐槽一下,好多网上的教程都是错的啊,什么直接走到头然后不能走了在当前的加油站加上油到下一个加油站就行,感觉根本就是欺负蓝桥杯的数据规模。事实上这个题完全想清楚还是不太容易的。下面讲题。

我们先分析数据知道,加油站有位置,油价。然后车的油箱有容量,猛一想并不是直接就能相通如何找到停留点的,具体看下面的分析。

我们先分析一下需要什么变量,车有可能有油,有可能没油,所以需要一个变量记录油箱剩下多少油。然后我们想一下,如果当前在第i个加油站,而只有行驶到第i+k个加油站油价更便宜,那么肯定停下在i+k点加油比较合算,那么我们肯定就要记录每次停下的位置(因为后面分析是贪心,贪心的思想就是将一个线段分成几份走完,局部最优求整体最优。)

首先确定如何判断无法到达目的地的情况:我们知道 车辆不加油的最大行驶距离== (油箱容量)c * (每升油的行驶距离)d2 那么当存在相邻两个加油站的间距比车辆不加油的最大行驶距离还大的话,到目标点是不可能完成的。

然后我们分析一下车辆的行驶情况

1.假如当前点为i,在车辆不加油的最大行驶距离内存在第i+k处油价更低

(1)油箱内剩余的油可以到达该位置

这种情况我们直接用剩下的油跑到i+k点就可以了,不花钱肯定是最节省的!

(2)油箱内剩余的油不够到达该位置

在这种情况是肯定是要加油的,而且无疑我们肯定是加当前点i的油是最划算的。那么应该加多少呢?加上油刚刚好到达i+k点是最好的! 因为i+k的油比i的油便宜,没必要在i位置多加油,如果后面需要在i+k加更划算。

2.假如当前点为i,在车辆不加油的最大行驶距离内不存在第i+k处油价更低

这个时候怎么办!我们加满油去跑一定划算,因为以i为起点在车辆不加油的最大行驶距离内 行驶一定找不到价格更低的加油站,我们一定是在价格相对最低的地方加油比较好。具体加油的多少还要看下一步能不能到更便宜的加油站,如果有参考1行进,如果没有继续参考2行进。

然后我的代码有一些特点,首先变量名比较长啊…海涵…然后是用结构体记录的第0位置是起点,第n+1位置是终点,终点的油价变量我设为0,可以理解为最便宜的,所以程序一定回到n+1点去!为什么说这个在程序实现的过程中就能体会到了。

我的具体代码基本上都注释了,如果有不明白的欢迎提问!

下面是我的丑代码:

#include<bits/stdc++.h>
using namespace std;
typedef struct node{
    double pos;
    double price;
}node;
node a[100001];
double last_oil;        //油箱剩余空间
double run_c;           //剩余油料可以行走的最大距离
int    best_pos;        //形成内最小油料价格的位置 
double full_run;        //邮箱在满了的情况下可以跑多远 
double use=0;
double d1,c,d2,p;
int n;
void run(){
    if(best_pos==n+1) return ;
    int flag=0;         //标记在最大行程内有没有找到费用更少的收费站 
    run_c=last_oil*d2;
    for(int i=best_pos+1;i<=n+1;i++){
        if(a[i].pos-a[best_pos].pos<=full_run){             //必须在full_run范围之内 
            if(a[i].price<=a[best_pos].price){              //如果是更优的选择 
                flag=1;                                     //标记 
                if(run_c>=a[i].pos-a[best_pos].pos){        //如果剩下的油已经够跑了 
                    last_oil=(a[i].pos-a[best_pos].pos)/d2; //剩余油量更新
                }
                else{                                       //如果剩下的油不够跑 
                    last_oil=0;                             //要恰好跑到价格更低的加油站 
                    use+=(a[i].pos-a[best_pos].pos-run_c)/d2*a[best_pos].price;
                }
                best_pos=i;                                 //更新最优节点 
                break;
            } 
        }
    }

    if(flag==0){                                //没有匹配到最优解 
        int better_pos=best_pos+1;
        for(int i=best_pos+1;i<=n+1;i++){       //一定是不可能到n+1的,因为n+1的在上面是一定可以匹配的!因为a[n+1].price是0! 
            if(a[i].pos-a[best_pos].pos<=full_run){
                if(a[i].price<a[better_pos].price){
                    better_pos=i;
                }
            }
        }
        //找到了更优值
        use+=(c-last_oil)*a[best_pos].price;                //一定是加满油最好了!因为在满油的行车范围内都到不了最优的,所以一定要加满油 
        last_oil=c-(a[better_pos].pos-a[best_pos].pos)/d2;  //先更新到达better_pos位置剩下的油料 
        best_pos=better_pos;
    } 
    run(); 
}

int main(){
    //freopen("input.txt","r",stdin);
    cin>>d1>>c>>d2>>p>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i].pos>>a[i].price;
    }
    a[0].pos=0;             //出发点 
    a[0].price=p;
    a[n+1].pos=d1;          //终点 
    a[n+1].price=0;

    last_oil=0;             //初始化邮箱剩余油量 
    best_pos=0;             //初始化初始位置 
    full_run=c*d2;

    for(int i=0;i<=n;i++){  //验证是否可达 
        if(a[i+1].pos-a[i].pos>full_run){
            cout<<"No Solution"<<endl;
            return 0;
        }
    }

    run();

    printf("%.2lf\n",use);
    return 0;
} 
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值