优化青海湖至景点X的租车路线成本 | 豆包MarsCode AI刷题

46 阅读4分钟

动态规划解析:最优加油站规划问题

问题分析

在开始解题之前,让我们先深入理解问题的核键点:

  1. 起点是青海湖,终点是一个远距离景点X
  2. 车辆初始和终点都需要保持200L的油量
  3. 油箱容量为400L,每公里耗油1L
  4. 沿途有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)

代码实现解析

这段代码的实现包含了几个关键步骤:

  1. 初始化处理:我们首先将起点(油价为0)和终点(油价为无穷大)添加到加油站列表中,并按距离排序。这样处理可以统一我们的状态转移逻辑。

  2. 状态数组初始化:创建dp数组,大小为(n+2) × 401,因为我们需要考虑起点和终点,以及0到400的所有可能油量。初始状态设置为dp[0][200] = 0,表示在起点时有200L油,花费为0。

  3. 状态转移:对于每个加油站,我们考虑:

    • 当前油量能否支持到达下一个加油站
    • 在当前加油站加多少油最优
    • 更新到达下一个加油站时的最小花费
  4. 结果输出:检查到达终点时油量不小于200L的所有状态,取最小值作为答案。如果不存在这样的状态,返回-1。

复杂度分析

  • 时间复杂度:O(n * C * C),其中n是加油站数量,C是油箱容量(这里是400)。对于每个加油站,我们需要枚举当前油量和可能加的油量。
  • 空间复杂度:O(n * C),用于存储动态规划的状态数组。

进阶思考

这个问题还可以进行一些扩展:

  1. 如果油箱容量不同怎么处理?
  2. 如果油耗不是固定的1L/km怎么改进?
  3. 是否可以通过剪枝来优化时间复杂度?

这些变体都可以基于当前的解决方案进行适当修改来解决,体现了动态规划的灵活性和实用性。