Erlo

P1016 [NOIP 1999 提高组] 旅行家的预算

2025-07-22 19:29:09 发布   48 浏览  
页面报错/反馈
收藏 点赞

这是一道经典的贪心算法问题。它考验的不仅仅是找到一个看似正确的贪心策略,更是对策略背后逻辑的严谨思考,以及对问题状态的完整建模。很多同学(包括你最初的代码)都会掉入同一个陷阱,这篇题解将带你绕开它。

一、问题描述

一辆汽车需要从起点行驶到终点,途中有若干加油站。给定汽车油箱容量、每升油能行驶的距离、以及每个加油站的油价和位置。目标是计算从起点到终点所需的最小花费。如果无法到达,则输出 "No Solution"。

二、核心思想:贪心策略的正确打开方式

贪心算法的精髓在于每一步都做出当前看起来最优的选择。那么,在这个问题里,当车停在某个加油站 i 时,“最优选择”到底是什么?

这需要分两种情况讨论:

情况一:在满油可达范围内,有比当前油站 i 更便宜的油站

  • 决策:既然前方有更便宜的油,我们显然不应该在当前这个较贵的站点加太多油。加多少最合适?刚好能开到那个最近的、比当前便宜的油站 j 即可
  • 理由:我们的目标是尽快用上更便宜的油。任何在当前站多加的、超出到达 j 站所需的油,都是在用“更贵的成本”行驶本可以由“更便宜成本”覆盖的距离,这显然不划算。

情况二:在满油可达范围内,所有油站都比当前油站 i 贵(或一样贵)

  • 决策:这说明当前站 i 的油是接下来一段路程中最划算的了。我们应该充分利用这个优势。所以,应该在当前站把油箱加满,然后径直开往这个可达范围内油价最低的那个油站 k
  • 理由:通过加满油,我们用当前相对便宜的油,行驶了最远的距离,最大限度地减少了未来在更贵的油站加油的需求。选择去 k 站,是为了让下一次决策时,我们的起点油价尽可能低。

三、警示录:常见的错误陷阱

陷阱一:忽略“剩余油量”,导致错误的成本计算(这是你代码的根本问题)

我最初的代码逻辑是:从站 i 找到目标站 j,然后计算 (a[j].dis - a[i].dis) * a[i].co / per 作为花费。这个公式隐含了一个致命的假设:每次出发时油箱都是空的

【致命场景】

  • A站(dist=0, price=5.0),B站(dist=400, price=10.0),终点C(dist=600)。
  • 油箱50升,每升跑10公里。满油跑500公里。
  1. 错误逻辑

    • 在A站,只能去B站。花费 (400/10) * 5.0 = 200 元。
    • 到达B站后(假设油箱空了),要去C站。花费 (200/10) * 10.0 = 200 元。
    • 总花费:400元
  2. 正确逻辑

    • 在A站,发现前方B站更贵。决策:加满油。花费 50 * 5.0 = 250 元。油箱现有50升。
    • 开车去B站,消耗40升油。到达B站时,油箱还剩10升!
    • 在B站,到终点C需要20升油。因为油箱里有10升,所以只需要再买10升昂贵的油。花费 10 * 10.0 = 100 元。
    • 总花费:250元(A站加油) + 100元(B站加油) = 350元

结论: 如果不追踪 current_oil(当前油量)这个状态,你的算法永远无法做出“用之前剩下的便宜油”这种决策,从而导致计算结果偏高。

陷阱二:错误的贪心目标

我最初代码的另一个问题是,在所有可达站里,总是选择那个油价最低的。这在某些情况下不是最优的。正确的做法是区分上述两种情况,做出不同的决策。

四、建模与实现

为了优雅地实现正确的贪心策略,我们可以做一些巧妙的建模:

  1. 统一站点:将起点终点都视为特殊的“加油站”。
    • 起点的距离为0,油价为初始油价。
    • 终点的距离为总距离,油价设为0(这能确保它总会被优先考虑,逻辑统一)。
  2. 排序:将所有站点(包括起点和终点)放入一个列表,并按距离从小到大排序。
  3. 追踪状态:在循环中,必须维护 current_oiltotal_cost 两个核心变量。

参考代码实现

import sys
class Oil:
    def __init__(self, co=0, dis=0):
        self.co = co
        self.dis = dis
input = lambda: sys.stdin.readline().strip()
dis, c, per, start_co, n = map(float, input().split())
n, maxn, a = int(n), c * per, [Oil(start_co, dis=0)] 
for i in range(n):
    x, y = map(float, input().split())
    a.append(Oil(y, x))
a.append(Oil(co=0, dis=dis))
a.sort(key=lambda item: item.dis)
n = len(a) - 1
if a[0].dis or a[1].dis - a[0].dis > maxn:
    print("No Solution")
    exit()
i, total_cost, current_oil = 0 , 0.0, 0.0
while i  maxn:
            break
        if a[j].co  maxn:
            break
        if a[j].co 

五、总结与反思

解决算法问题,尤其是贪心问题时:

  1. 策略要严谨:不能只凭直觉。需要仔细分析不同情况,并证明每种情况下的局部最优选择。
  2. 状态要完整:问问自己,“我的程序需要记住什么信息才能做出下一步决策?”。在这个问题里,current_oil 就是被遗忘的关键记忆。
  3. 建模要巧妙:好的数据建模(如将起点终点视为普通站点)可以大大简化逻辑,避免复杂的边界条件判断。

登录查看全部

参与评论

评论留言

还没有评论留言,赶紧来抢楼吧~~

手机查看

返回顶部

给这篇文章打个标签吧~

棒极了 糟糕透顶 好文章 PHP JAVA JS 小程序 Python SEO MySql 确认