PAT 1033 To Fill or Not to Fill (25分) 贪心思想

题目

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.

Input Specification:
Each input file contains one test case. For each case, the first line contains 4 positive numbers: C​max​​ (≤ 100), the maximum capacity of the tank; D (≤30000), the distance between Hangzhou and the destination city; D​avg​​ (≤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: P​i​​ , the unit gas price, and D​i​​ (≤D), the distance between this station and Hangzhou, for i=1,⋯,N. All the numbers in a line are separated by a space.

Output Specification:
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.

Sample Input 1:
50 1300 12 8
6.00 1250
7.00 600
7.00 150
7.10 0
7.20 200
7.50 400
7.30 1000
6.85 300
Sample Output 1:
749.17
Sample Input 2:
50 1300 12 2
7.10 0
7.00 600
Sample Output 2:
The maximum travel distance = 1200.00

题目解读

题目大意:汽车从杭州出发可以通过高速公路去任何城市,但是油箱的容量是有限的,路上有很多加油站,每个加油站的价格不同,为汽车设计一个从杭州到终点的最便宜的加油策略。

输入:第一行:Cmax表示油箱最大容量,D表示杭州到目的地的距离,Davg表示平均每单位的汽油可以让汽车行驶的距离,N表示途中加油站的数量;接下来 N 行:给出给个加油站的单位油价Pi和杭州(起点)到这个站点的距离Di

输出:求汽车从杭州到终点的最少花费(精确到两位小数)。如果不能够到达,就输出汽车能够行驶的最大距离(精确到两位小数)。

思路分析

核心思想:贪心算法(每次寻找局部最优,在最便宜的加油站加最多的油)。

前期准备:

  • 最终输出无论是距离还是价格都要求精确到两位小数,虽然从给出的输入数据来看,距离、邮箱容量等好像都是整数,但为了操作方便,避免运算过程精度丢失,我们全都用double保存。(再说了,它给出的数据说不定就是坑你的呢?)
  • 设置结构体数组保存每个加油站的单价和到杭州的距离。
  • 按照到杭州的距离对结构体数组排序,因为输入是无序的。
  • 排序完判断第1个结构体到杭州的距离是否为0,也就是说最近的加油站是不是在起点。因为题目说了假定刚开始邮箱没有油,那么如果起点处没有加油站,就比欸想开车了,直接输出并返回吧。

贪心核心:怎么实现每次都做出局部最优的选择?

对于任意一个站点:如果我们在这个站点加满油,那么最多就可以跑cmax*davg的距离,我们对这个距离段中遇到的加油站情况进行分析:

  • 按顺序遍历【当前位置,当前位置+cmax*davg】中的所有加油站,如果某个加油站的收费低于当前站点,那么我就在当前站点加油,跑到那个站点去,加多少呢?就加能恰好让我到达那个加油站的油。这样我去那个站点加油就能更便宜。
  • 如果当前站点后面有不止一个站点更便宜,怎么选择?比如我现在在A,价格是10,后面是 B 价格9, 后面是C 价格8, 我先找到的是B,那我就退出本次循环,刚好加油跑到B去,在B处重新继续分析。为啥不直接加油去C,如果从当前位置直接加油去C,那么BC之间的花费单价是当前加油站的价格也就是10,但我如果先去了B,那么从B到C的油价就是B处的价格9,显然更便宜。这样才满足局部最优。
  • 如果当前位置后面没有更便宜的加油站呢?
    • 如果我在当前位置最多能达到的最远距离超过了终点,那么我直接加油跑到终点,因为后面的站点只会更贵。
    • 如果我不能直接到终点,那么我肯定是需要加油的,那我就找尽可能地找比较便宜的那个加油站,在当前加油站加满油之后过去。既然没有比当前更低价格的了,就让油箱加到最大值,这样能保证利益最大化,保证最大的距离使用的是便宜的油。
  • 如果当前位置不能直接到达终点,并且后面没有加油站了呢?那么肯定不能到达中终点了,只能到达当前位置+cmax*davg,也就是说在当前位置加满,能跑多远是多远。

总结:

  • 当前位置能到达的范围中如果存在更便宜的加油站,就加合适的油刚好到达那个加油站。
  • 如果不存在更便宜的,但是当前位置能直接到达终点,那就加合适的油到达终点。
  • 不存在更便宜的,并且不能直接到终点,找到可达的加油站的相对而言最便宜那个,在当前位置加满油,然后去那个站点。
  • 当前位置不能到终点,并且后面没有加油站了,输出能到达的最大距离。

代码

#include <iostream>
#include <algorithm>
using namespace std;

struct Station {
    // 每单位汽油价格
    double price;
    // 从起点到这里的距离
    double dis;
}stat[500];

// 对加油站 离起点 从近到远 排序
bool cmp(Station a, Station b) {
    return a.dis < b.dis;
}

int main() {
    // cmax油箱容量 d 距离 davg 每单位油能跑多远 n个加油站
    double cmax, d, davg;
    int n;
    cin >> cmax >> d >> davg >> n;
    // 每个加油站的 价格 位置
    for (int i = 0; i < n; ++i) 
        cin >> stat[i].price >> stat[i].dis;
    // 根据离起点的距离排序
    sort(stat, stat + n, cmp);
    // 第一个加油站不在起点,无法起步
    if(stat[0].dis != 0) {
        printf("The maximum travel distance = 0.00");
        return 0;
    } 
    // nowpos当前在哪个加油站,
    int nowpos = 0;
    // nowgas 当前剩余多少油,total_price 总花费
    double nowgas = 0.00, total_price = 0.00;
    // 油箱加满一次油,可以跑多远
    double each_max_run_dis = cmax * davg;
    // 是否到达终点
    bool arrived = false;
    while (!arrived) {
        // 遍历 【从当前加油站到最远能跑到的位置】 之间的 全部加油站
        bool exist_stat  = false; // 当前位置后面是否存在可达加油站
        bool exist_cheaper_stat = false; // 是否存在比当前位置更便宜的加油站
        // 不存在比当前便宜的,就找到其中价格最低的
        int min_pos = -1; double min_price = 9999.99;
        for (int i = nowpos + 1; i < n; ++i) {
            // 当前位置到不了这个加油站,退出循环
            if (stat[i].dis - stat[nowpos].dis > each_max_run_dis) {
                // 最多还能走 nowgas * davg
                break;
            }
            exist_stat = true; // 存在可达加油站
            // 如果有比当前加油站价格更低的加油站
            // 算一下恰好跑到那个加油站需要多少油,
            // 加油跑到那个加油站
            if (stat[i].price < stat[nowpos].price) {
                // 设置标志
                exist_cheaper_stat = true;
                double needgas = (stat[i].dis - stat[nowpos].dis) / davg - nowgas;
                // 加这么多油,刚好跑到那个加油站,算一下花费
                total_price += stat[nowpos].price * needgas;
                // 到达那个位置后剩余油量归0
                nowgas = 0;
                // 改变位置
                nowpos = i;
                // 不再遍历后面的加油站
                // 比如我现在 在A,价格是10,后面是 B 价格9, 后面是C 价格8
                // 我先找到的是B,那我就刚好加油跑到B去,在B处重新考虑
                // 为啥不直接加油去C,如果从当前位置直接加油去C,那么BC之间的花费单价是当前加油站的价格也就是10
                // 但我如果先去了B,那么从B到C的油价就是B处的价格9,显然更便宜
                // 这样才满足局部最优
                break;
            }
            // 如果说我能从当前位置跑1000米,但是在此之间的加油站的价格没有一个比我现在的价格低
            // 那我就尽量找最便宜的那个,然后在当前位置加满油,跑到相对而言最便宜的那个加油站去加油
            // 这样才满足局部最优(在最便宜的位置加更多的油跑最多的距离)
            // 这个if不会和上面的if同时执行(上面执行完就break了),所以不用加else
            if (stat[i].price < min_price) {
                min_pos = i;
                min_price = stat[i].price;
            }
        }
        // 不存在比当前便宜的,但是当前位置最远能达到终点
        if (!exist_cheaper_stat && (d - stat[nowpos].dis <= each_max_run_dis)) {
            double needgas = (d - stat[nowpos].dis) / davg - nowgas;
            // 加这么多油,刚好跑到终点,算一下花费
            total_price += stat[nowpos].price * needgas;
            // 到达终点
            arrived = true;
            break;
        }
        // 不存在比当前便宜的,但是找到了其他加油站中相对最便宜那个
        if (!exist_cheaper_stat && exist_stat) {
            // 后面有加油站,但是都比当前位置的加油站贵
            // 那我就尽量找最便宜的那个,然后在当前位置加满油,跑到相对而言最便宜的那个加油站去加油
            // 这样才满足局部最优(在最便宜的位置加更多的油跑最多的距离)
            double needgas = cmax - nowgas;
            // 在当前位置加满油,算一下花费
            total_price += stat[nowpos].price * needgas;
            // 到达那个位置后的剩余油量
            nowgas = cmax - (stat[min_pos].dis - stat[nowpos].dis) / davg;
            // 改变位置
            nowpos = min_pos;
        // 当前位置无法抵达下一个加油站
        } else if (!exist_stat){
            // 最多还能走 cmax * davg
            printf("The maximum travel distance = %.2f", stat[nowpos].dis + each_max_run_dis);
            return 0;
        }
    }
    // while正常结束,说明到达终点
    printf("%.2f", total_price);
    return 0;
}