#LC2187. 完成旅途的最少时间 (trips)

完成旅途的最少时间 (trips)

你好,同学。我是你的 OI 金牌教练。今天我们要深入探讨的是一道非常经典、也是 NOI 系列竞赛中出镜率极高的题型——“二分答案”。

这道题 LeetCode 2187 在竞赛界被称为“资源调度”或“任务分配”类问题。它完美体现了如何将一个复杂的“寻找最小值”问题,转化为简单的“可行性判定”问题。

请拿出草稿纸,随我一起拆解。


一、 预备知识点

  1. 二分答案 (Binary Search on Answer):本题的核心。当答案具有单调性(时间越长,完成的旅途总数越多)时使用。
  2. 单调性分析:确定函数 f(t)f(t)(时间 tt 内完成的总次数)是随 tt 递增的。
  3. 大整数溢出处理 (long long):NOI 竞赛中的易错点。本题的时间范围可达 101410^{14},必须使用 long long
  4. 计算复杂度理论:理解为什么 O(NlogM)O(N \log M) 比暴力搜索更优。

二、 题目描述 (NOI 风格)

题目名称:完成旅途的最少时间 (trips) 输入文件trips.in 输出文件trips.out

【问题描述】 给你一个数组 time ,其中 time[i] 表示第 ii 辆公交车完成一趟旅途所需要的时间。 每辆公交车可以连续完成多趟旅途,即在一趟旅途完成后可以立即开始下一趟旅途。每辆公交车之间互不影响。 同时给你一个整数 totalTrips ,表示所有公交车总共需要完成的旅途数目。 请你计算并输出完成至少 totalTrips 趟旅途所需要的最少时间

【输入格式】 第一行包含两个正整数 nntotalTripstotalTrips,分别表示公交车的数量和需要完成的总旅途数。 第二行包含 nn 个由空格隔开的正整数,表示每辆车完成一趟旅途的时间。

【输出格式】 输出一个整数,表示完成任务的最少时间。

【样例 1 输入】

3 5
1 2 3

【样例 1 输出】

3

【样例 1 说明】

  • t=1t=1:总数 = $\lfloor 1/1 \rfloor + \lfloor 1/2 \rfloor + \lfloor 1/3 \rfloor = 1 + 0 + 0 = 1$
  • t=2t=2:总数 = $\lfloor 2/1 \rfloor + \lfloor 2/2 \rfloor + \lfloor 2/3 \rfloor = 2 + 1 + 0 = 3$
  • t=3t=3:总数 = $\lfloor 3/1 \rfloor + \lfloor 3/2 \rfloor + \lfloor 3/3 \rfloor = 3 + 1 + 1 = 5$ 最少需要时间 3。

【样例 2 输入】

1 1
2

【样例 2 输出】

2

【数据范围限制】

  • 1n1051 \le n \le 10^5
  • 1time[i],totalTrips1071 \le time[i], totalTrips \le 10^7

三、 启发式引导:草稿纸上的推演过程

1. 寻找单调性

请在纸上画一个坐标系:横轴是“花费的时间 TT”,纵轴是“能完成的总旅途数 SS”。 你会发现:随着 TT 的增加,SS 绝对不会减少。

  • 如果时间 T=100T=100 足够完成任务,那么 T=101T=101 也一定足够。
  • 结论:答案存在明显的单调分界点。

2. 确定二分边界(极其重要)

  • 左边界 (Low):1(最少也要 1 单位时间)。
  • 右边界 (High)
    • 考虑最坏情况:只有一辆车,且它是最慢的那辆(比如 10710^7),要跑 10710^7 趟。
    • 时间可能达到 101410^{14}。所以 High 应该设为 101410^{14} 或更准确地设为 min(time) * totalTrips

3. 验证函数 (Check)

假设当前二分到的时间是 mid

  • ii 辆车能跑多少趟? 答案是 mid/time[i]\lfloor mid / time[i] \rfloor
  • 将所有车的趟数累加。如果累加和 totalTrips\ge totalTrips,说明 mid 时间足够。

四、 算法思路流程图

在 Mermaid 语法中,我们避开特殊字符以确保渲染正确:

graph TD
    Start(开始) --> ReadData(读取 n 和 totalTrips)
    ReadData --> InitBound(Low = 1, High = 10 的 14 次方)
    InitBound --> Loop{Low 小于等于 High}
    
    Loop -- 是 --> CalcMid(Mid = Low + High 的一半)
    CalcMid --> CheckFunc(计算 Mid 时间内能完成的总趟数 Sum)
    
    CheckFunc --> Judge{Sum 是否大于等于 totalTrips}
    Judge -- 是 --> Record(Ans = Mid, High = Mid - 1)
    Judge -- 否 --> MoveLow(Low = Mid + 1)
    
    Record --> Loop
    MoveLow --> Loop
    Loop -- 否 --> Output(输出 Ans)
    Output --> End(结束)

    subgraph 验证逻辑
    CheckFunc --- C1(遍历 time 数组)
    C1 --- C2(Sum 累加 Mid 除以 time-i)
    C2 --- C3(注意 Sum 溢出建议提前跳出循环)
    end

五、 复杂度分析与优化

1. 时间复杂度分析

  • 二分次数log2(1014)47\log_2(10^{14}) \approx 47 次。
  • Check 函数:每次 O(n)O(n)
  • 总复杂度:$O(n \log(\text{max\_time} \cdot \text{totalTrips}))$。
  • n=105n=10^5 规模下,运算量约 5×1065 \times 10^6,远低于 NOI 的 1 秒限制(通常 10810^8)。

2. 空间复杂度分析

  • 仅需存储 time 数组,复杂度 O(n)O(n)

3. 优化建议(易错点)

  • Long LongtotalTrips 虽然是 10710^7,但 Sum 的计算过程会远超 23112^{31}-1,必须用 long long
  • Check 剪枝:在 Sum 累加过程中,如果 Sum 已经大于等于 totalTrips,可以立即返回 true,防止 long long 也发生溢出。

六、 总结:读题关键词

在 NOI 赛场上,看到这些词就要联想到“二分答案”:

  1. “最少时间” / “最大值最小” / “最小值最大”:这是二分答案的最强标志。
  2. “完成至少 X 个任务”:这定义了单调性的方向。
  3. “每辆车互不影响”:这意味着总数可以直接相加,判定逻辑简单。
  4. 数据范围 101410^{14} 潜在值:暗示你必须注意数据类型,以及不能使用 O(T)O(T) 的暴力枚举,只能使用 O(logT)O(\log T) 的二分。

同学,思路清晰了吗?现在请在草稿纸上尝试写出 Check 函数的细节,注意处理那几个 long long 的溢出可能。加油!