最优加油方案问题题解
问题描述
小F计划从青海湖出发,前往一个遥远的景点X进行旅游。景点X可能是“敦煌”或“月牙泉”,线路的路径是唯一的。由于油价的不断上涨,小F希望尽量减少行程中的燃油成本。车辆的油箱容量为400L,在起始点租车时,车内剩余油量为200L。每行驶1km消耗1L油。沿途设有多个加油站,小F可以在这些加油站补充燃油;此外,到达目标景点X还车的时候,需要保证车内剩余的油至少有200L。合理规划加油站的加油顺序和数量,计算最小化从青海湖到景点X的旅行成本(元)。
输入:
distance:从青海湖到景点X的总距离(km),距离最远不超过10000 km。n:沿途加油站的数量 (1 <= n <= 100)gas_stations:每个加油站的信息,包含两个非负整数[加油站距离起始点的距离(km), 该加油站的油价(元/L)]
输出:
- 最小化从青海湖到景点X的旅行成本(元)。如果无法到达景点X,或者到达景点X还车时油料剩余不足200L,则需要返回
-1告诉小F这是不可能的任务。
解题思路
1. 判断可解情况
首先需要判断几种不可能完成任务的情况:
- 如果没有加油站,显然无法完成任务。
- 如果最后一个加油站距离终点太远(最后一个加油站位置 + 200L续航里程 > 400L总容量),也无法完成任务。
2. 动态规划设计
状态定义
dp[i][j] 表示:使用前i个加油站,当前油量为j时的最小花费。
转移方程
对于每个加油站i,有两种选择:
-
不在当前加油站加油:
dp[i][j] = dp[i-1][j] + disdis是当前加油站到下一个加油站(或终点)的距离。
-
在当前加油站加油:
dp[i][j] = min(dp[i][j], dp[i][j-1] + price)price是当前加油站的油价。
边界条件
- 初始状态:
dp[0][200] = 0(起始油量200L,花费为0)。 - 不可能状态:初始化为无穷大。
3. 实现细节
- 对加油站按距离排序,方便计算相邻加油站之间的距离。
- 需要特别处理第一个加油站到终点的距离。
- 对于油箱容量超出400L的情况要排除。
- 最终结果需要判断是否存在可行解(是否为无穷大)。
实现代码
普通动态规划
def solution(distance, n, gas_stations):
# 如果没有加油站, 直接返回Impossible
if n == 0:
return -1
gas_stations.sort(key=lambda x: x[0], reverse=True)
# 如果最后一个加油站的距离+200, 剩余油量无法满足条件, 直接返回Impossible
if gas_stations[n - 1][0] + 200 > 400:
return -1
# 按照距离排序, 距离为加油站与目的地的距离
dp = [[float('inf')] * 401 for _ in range(n + 1)]
dp[0][200] = 0
flag = 1
for i in range(1, n + 1):
for j in range(0, 401):
# 有些加油站不在 0-distance 之间, 直接跳过
if gas_stations[i - 1][0] > distance:
dp[i][200] = 0
continue
if flag == 1 or i == 1:
dis = distance - gas_stations[i - 1][0]
flag = 0
else:
dis = gas_stations[i - 2][0] - gas_stations[i - 1][0]
# 更新dp[i][j]: i个加油站时, 油量为j, 最小花费
# 本站不加油 or 本站加油
if dis + j <= 400: # 对于dis + j > 400的状态, 无法通过上一个加油站加油达成
dp[i][j] = dp[i - 1][j + dis]
if j != 0:
dp[i][j] = min(dp[i][j], dp[i][j - 1] + gas_stations[i - 1][1])
res = dp[n][gas_stations[n - 1][0] + 200]
if res == float('inf'):
return -1
return int(res)
复杂度分析
- 时间复杂度:O(n)
- 空间复杂度:O(n)
空间优化
使用滚动数组可以进一步将空间复杂度降为O(1)。
def solution(distance, n, gas_stations):
if n == 0:
return -1
# 按照距离排序(从远到近)
gas_stations.sort(key=lambda x: x[0], reverse=True)
# 检查是否能到达终点
if gas_stations[n - 1][0] + 200 > 400:
return -1
# 使用滚动数组,只保存两行
curr = [float('inf')] * 401
prev = [float('inf')] * 401
# 初始状态
prev[200] = 0
flag = 1
for i in range(1, n + 1):
# 复制前一个状态
curr = [float('inf')] * 401
# 如果加油站超出范围,跳过
if gas_stations[i - 1][0] > distance:
curr[200] = 0
prev = curr
continue
# 计算距离
if flag == 1 or i == 1:
dis = distance - gas_stations[i - 1][0]
flag = 0
else:
dis = gas_stations[i - 2][0] - gas_stations[i - 1][0]
# 状态转移
for j in range(401):
# 不加油的情况
if dis + j <= 400:
curr[j] = prev[j + dis]
# 加油的情况
if j > 0:
curr[j] = min(curr[j], curr[j - 1] + gas_stations[i - 1][1])
# 更新prev数组
prev = curr
# 检查最终结果
res = curr[gas_stations[n - 1][0] + 200]
return -1 if res == float('inf') else int(res)