1 青海湖租车之旅
油价飞升的今天,我们尽量减少花费。我们出门旅游,有时候租车去旅游也是一种不错的方式。这次我们这次旅游是从「青海湖」到「景点 X」,景点 X 可以是「敦煌」、「月牙泉」等,线路的路径是唯一的,假设我们每走 1 km 消耗 1 L 的油,车油箱容量 400L。比如:如果「景点 X」是敦煌,我们在青海湖租车前油箱是 200L 的,在「景点 X」(敦煌)还车的时候也是 200L 的,路上有很多加油站,加油站在青海湖和「景点 X」的连线上。
输入格式
第 1 行表示「青海湖」到「景点 X」的距离,距离最远不超过 10000 km。 第 2 行表示接下来 N 行表示 N 个加油站(N 为正整数)。 接下来 N(1 <= N <= 100)行表示,每一个加油站情况。每一个加油站包括距离「景点 X」的距离 a km(0 <= a <= 10000),以及每升汽油的价格 b 元(0 <= b <= 2000),a 和 b 均为正整数。
输出格式
如果不能到达目的地「景点 X」,输出 Impossible。 如果能到达目的地「景点 X」,输出最小花费多少元。
输入样例: 500 4 100 1 200 30 400 40 300 20
输出样例: 4300
解题思路
要解决这个问题,我们需要找到从「青海湖」到「景点 X」的最小花费路径,考虑油箱容量、初始油量、最终油量要求以及沿途加油站的油价。以下是详细的解题思路:
1. 问题分析
- 总距离(D) :从「青海湖」到「景点 X」的总距离,单位为公里(km)。
- 油箱容量:400 升。
- 初始油量:200 升。
- 最终油量:到达「景点 X」时,油箱中至少需要保留 200 升油。
- 油耗:每公里消耗 1 升油。
- 加油站信息:每个加油站提供距离「景点 X」的距离(a km)和油价(b 元/升)。
2. 转换加油站位置
为了便于计算,我们将所有加油站的位置转换为相对于起点(「青海湖」)的距离:
distanceFromStart = D - a
这样,我们可以按从起点到终点的顺序处理加油站。
3. 排序加油站
将所有加油站按距离起点的距离升序排序。然后,添加一个虚拟的终点加油站,位置为 D km,油价为 0 元/升,以方便算法处理终点的情况。
4. 贪心算法策略
采用贪心算法,通过在油价较低的加油站尽可能多地加油,以减少总花费。具体策略如下:
-
初始化参数:
- 当前油量(currentFuel)为初始油量 200 升。
- 总花费(totalCost)为 0 元。
- 上一个加油站的位置(prevDistance)为 0 km。
-
遍历每个加油站:
-
计算从上一个加油站到当前加油站的距离(distanceToStation)。
-
更新当前油量:
currentFuel -= distanceToStation -
如果当前油量小于 0,则无法到达当前加油站,输出
Impossible。 -
寻找下一个油价更低的加油站:
-
从当前加油站开始,寻找下一个油价低于当前加油站的加油站,且在油箱容量(400 km)可达范围内。
-
如果找到这样的加油站:
-
计算到达该加油站所需的油量(distanceToCheaper)。
-
特殊情况:如果找到的是终点加油站,确保到达终点时油量至少为 200 升。
-
计算需要加油的量:
requiredFuel = distanceToCheaper + (终点需要保留的油量,如果是终点) fuelToBuy = requiredFuel - currentFuel fuelToBuy = min(fuelToBuy, tankCapacity - currentFuel) -
更新总花费和当前油量:
totalCost += fuelToBuy * currentStation.price currentFuel += fuelToBuy
-
-
如果没有找到更便宜的加油站:
-
尽可能多地加油,以满足到达下一个加油站或终点的需求,同时考虑最终需要保留的油量。
-
计算需要加油的量:
neededFuel = (distanceToEnd + finalFuel) - currentFuel fuelToBuy = min(neededFuel, tankCapacity - currentFuel) -
更新总花费和当前油量:
totalCost += fuelToBuy * currentStation.price currentFuel += fuelToBuy
-
-
-
处理油价为 0 的加油站:
-
如果当前加油站的油价为 0(且不是终点),应尽可能多地加油,因为加油成本为 0 元。
-
加油量为:
fuelToBuy = min(tankCapacity - currentFuel, distanceToEnd + finalFuel - currentFuel) -
更新当前油量(无需更新总花费,因为油价为 0)。
-
-
更新上一个加油站的位置:
prevDistance = currentStation.distance
-
-
最终检查:
- 遍历完所有加油站后,检查到达终点时油量是否至少为 200 升。
- 如果满足条件,输出
totalCost。 - 否则,输出
Impossible。
5. 处理特殊情况
-
没有加油站:
- 如果没有任何加油站,且初始油量不足以直接到达终点(D ≤ initialFuel - finalFuel),则输出
Impossible。
- 如果没有任何加油站,且初始油量不足以直接到达终点(D ≤ initialFuel - finalFuel),则输出
-
加油站信息不完整:
- 如果某个加油站的输入信息不完整(例如只有距离,没有油价),则视为无效,加油站不可用,可能导致无法完成旅程。
-
油价为 0 的普通加油站:
- 除终点外,油价为 0 的加油站应作为普通加油站处理,可以免费加油,但仍需遵循油箱容量和最终油量的限制。
算法实现
import java.util.*;
public class Main {
// 定义加油站类
static class GasStation implements Comparable<GasStation> {
int distance; // 距离起点的距离
int price; // 每升价格
GasStation(int distance, int price) {
this.distance = distance;
this.price = price;
}
// 按距离升序排序
public int compareTo(GasStation other) {
return this.distance - other.distance;
}
}
public static String solution(int distance, int n, List<List<Integer>> gasStations) {
// Please write your code here
// 初始化加油站列表
List<GasStation> stations = new ArrayList<>();
// 读取并转换加油站信息
for (int i = 0; i < n; i++) {
List<Integer> station = gasStations.get(i);
if (station.size() != 2) {
// 加油站信息不完整,无法继续
return "Impossible";
}
int a = station.get(0); // 距离终点的距离
int b = station.get(1); // 油价
int distanceFromStart = distance - a;
if (distanceFromStart < 0 || distanceFromStart > distance) {
// 无效的加油站位置,忽略
continue;
}
stations.add(new GasStation(distanceFromStart, b));
}
// 添加终点作为一个虚拟加油站,价格为 0
stations.add(new GasStation(distance, 0));
// 按距离升序排序
Collections.sort(stations);
// 初始化参数
int tankCapacity = 400; // 油箱容量
int initialFuel = 200; // 初始油量
int finalFuel = 200; // 终点需要保留的油量
int currentFuel = initialFuel;
long totalCost = 0; // 使用 long 防止溢出
int prevDistance = 0;
// 遍历每个加油站
for (int i = 0; i < stations.size(); i++) {
GasStation currentStation = stations.get(i);
int distanceToStation = currentStation.distance - prevDistance;
currentFuel -= distanceToStation;
if (currentFuel < 0) {
// 油量不足以到达当前加油站
return "Impossible";
}
// 如果是最后一个加油站(终点),不需要加油
if (i == stations.size() - 1) {
// 检查是否有足够的油到达终点并保留 finalFuel
if (currentFuel < finalFuel) {
// 需要额外加油,但终点不可加油
return "Impossible";
}
// 已到达终点
break;
}
// 寻找下一个油价更低的加油站
int minPrice = currentStation.price;
int minPriceIndex = -1;
for (int j = i + 1; j < stations.size(); j++) {
GasStation nextStation = stations.get(j);
int distanceToNext = nextStation.distance - currentStation.distance;
if (distanceToNext > tankCapacity) {
// 超出油箱容量,无法到达
break;
}
if (nextStation.price < minPrice) {
minPrice = nextStation.price;
minPriceIndex = j;
break;
}
}
if (minPriceIndex != -1) {
// 找到了下一个油价更低的加油站
GasStation cheaperStation = stations.get(minPriceIndex);
int distanceToCheaper = cheaperStation.distance - currentStation.distance;
// 判断是否是虚拟终点加油站
boolean isVirtualEndpoint = (cheaperStation.distance == distance && cheaperStation.price == 0);
// 计算所需油量
int requiredFuel = distanceToCheaper;
if (isVirtualEndpoint) {
// 如果是终点,确保到达终点后有 finalFuel
requiredFuel += finalFuel;
}
if (requiredFuel > tankCapacity) {
// 无法到达更便宜的加油站(包括终点需要的 finalFuel)
return "Impossible";
}
if (requiredFuel > currentFuel) {
// 需要加油
int fuelToBuy = requiredFuel - currentFuel;
// 确保不会超过油箱容量
fuelToBuy = Math.min(fuelToBuy, tankCapacity - currentFuel);
totalCost += (long) fuelToBuy * currentStation.price;
currentFuel += fuelToBuy;
// 调试输出
// System.out.println("加油站位置: " + currentStation.distance + " km, 加油量: " + fuelToBuy + " L, 总花费: " + totalCost);
}
// 特殊处理:如果当前加油站油价为 0,尽可能多加油
if (currentStation.price == 0 && !isVirtualEndpoint) {
int maxFuelCanBuy = tankCapacity - currentFuel;
// 需要加的油量是 tankCapacity - currentFuel
int fuelToBuy = Math.min(maxFuelCanBuy, distance - currentStation.distance - finalFuel);
fuelToBuy = Math.max(fuelToBuy, 0); // 不能为负
totalCost += (long) fuelToBuy * currentStation.price; // 这里 fuelToBuy * 0 = 0
currentFuel += fuelToBuy;
// 虽然油价为0,但需要确保不超过所需的油量
}
} else {
// 没有找到更便宜的加油站,尽可能加满
int distanceToEnd = distance - currentStation.distance;
// 需要的油量是到终点所需的油量加上最终需留的油量
int neededFuel = distanceToEnd + finalFuel;
// 能买的最大油量
int maxFuelCanBuy = tankCapacity - currentFuel;
// 需要加的油量
int fuelToBuy = Math.min(neededFuel - currentFuel, maxFuelCanBuy);
fuelToBuy = Math.max(fuelToBuy, 0); // 不能为负
totalCost += (long) fuelToBuy * currentStation.price;
currentFuel += fuelToBuy;
// 特殊处理:如果当前加油站油价为 0,尽可能多加油
if (currentStation.price == 0) {
int additionalFuel = Math.min(tankCapacity - currentFuel, distanceToEnd + finalFuel - currentFuel);
additionalFuel = Math.max(additionalFuel, 0);
totalCost += (long) additionalFuel * currentStation.price; // 0元,不影响总花费
currentFuel += additionalFuel;
}
}
// 更新前一个加油站的位置
prevDistance = currentStation.distance;
}
// 检查是否在终点有足够的油量
if (currentFuel < finalFuel) {
return "Impossible";
}
return String.valueOf(totalCost);
}
public static void main(String[] args) {
List<List<Integer>> gasStations1 = new ArrayList<>();
gasStations1.add(List.of(100, 1));
gasStations1.add(List.of(200, 30));
gasStations1.add(List.of(400, 40));
gasStations1.add(List.of(300, 20));
List<List<Integer>> gasStations2 = new ArrayList<>();
gasStations2.add(List.of(100, 999));
gasStations2.add(List.of(150, 888));
gasStations2.add(List.of(200, 777));
gasStations2.add(List.of(300, 999));
gasStations2.add(List.of(400, 1009));
gasStations2.add(List.of(450, 1019));
gasStations2.add(List.of(500, 1399));
List<List<Integer>> gasStations3 = new ArrayList<>();
gasStations3.add(List.of(101));
gasStations3.add(List.of(100, 100));
gasStations3.add(List.of(102, 1));
List<List<Integer>> gasStations4 = new ArrayList<>();
gasStations4.add(List.of(34, 1));
gasStations4.add(List.of(105, 9));
gasStations4.add(List.of(9, 10));
gasStations4.add(List.of(134, 66));
gasStations4.add(List.of(215, 90));
gasStations4.add(List.of(999, 1999));
gasStations4.add(List.of(49, 0));
gasStations4.add(List.of(10, 1999));
gasStations4.add(List.of(200, 2));
gasStations4.add(List.of(300, 500));
gasStations4.add(List.of(12, 34));
gasStations4.add(List.of(1, 23));
gasStations4.add(List.of(46, 20));
gasStations4.add(List.of(80, 12));
gasStations4.add(List.of(1, 1999));
gasStations4.add(List.of(90, 33));
gasStations4.add(List.of(101, 23));
gasStations4.add(List.of(34, 88));
gasStations4.add(List.of(103, 0));
gasStations4.add(List.of(1, 1));
// System.out.println(solution(500, 4, gasStations1) == "4300");
// System.out.println(solution(500, 7, gasStations2) == "410700");
// System.out.println(solution(500, 3, gasStations3) == "Impossible");
// System.out.println(solution(100, 20, gasStations4) == "0");
// System.out.println(solution(100, 0, new ArrayList<>()) == "Impossible");
System.out.println(solution(500, 4, gasStations1).equals("4300"));
System.out.println(solution(500, 7, gasStations2).equals("410700"));
System.out.println(solution(500, 3, gasStations3).equals("Impossible"));
System.out.println(solution(100, 20, gasStations4).equals("0"));
System.out.println(solution(100, 0, new ArrayList<>()).equals("Impossible"));
}
}