#LC2073. 买票需要的时间

买票需要的时间

你好,同学!欢迎来到 NOI 数据结构专项训练。

今天我们要攻克的是一个关于“队列模拟”与“贡献法思想”的入门好题:买票需要的时间。这道题虽然在 LeetCode 上被归类为简单题,但在 OI 竞赛中,它是理解“离散时间模拟”和“逻辑分类讨论”的绝佳素材。


一、 预备知识点

在动手之前,请确保你已经掌握以下内容:

  1. 队列(Queue)的逻辑:先进先出(FIFO)。虽然本题可以用数学方法直接解,但模拟法的核心是队列。
  2. 模拟(Simulation):按照题目描述的步骤,一步步在代码中复现过程。
  3. 取最小值函数 std::min:用于处理每个人实际贡献的买票次数。
  4. 循环与分支判断:处理排在目标人物之前和之后的逻辑差异。

二、 NOI 竞赛题目描述

题目名称:买票需要的时间 (Time Needed to Buy Tickets) 时间限制:1.0s 内存限制:256MB

【问题描述】nn 个人正在排队买票,其中第 00 个人排在队伍最前面,第 (n1)(n - 1) 个人排在队伍最后面。

每个人买一张票需要消耗 11 秒。一旦一个人买到一张票,如果他还需要购买更多的票,他会立即走到队伍的最后面;如果他已经买完了所有需要的票,他就会离开队伍。

给定一个长度为 nn 的整数数组 tickets,其中 tickets[i] 表示第 ii 个人需要购买的票数。再给定一个整数 kk,表示我们需要观察的目标人物的索引(从 00 开始)。

请计算并返回第 kk 个人买完所有票所需要的总时间

【输入格式】 输入包含两行。 第一行包含两个整数 nnkk,分别表示人数和目标人物的索引。 第二行包含 nn 个整数,表示 tickets 数组。

【输出格式】 输出一个整数,表示目标人物 kk 买完票所需的总秒数。

【样例输入】

3 2
2 3 2

【样例输出】

6

【数据规模与约定】

  • 1n1001 \le n \le 100
  • 1tickets[i]1001 \le tickets[i] \le 100
  • 0k<n0 \le k < n

三、 启发式思路引导:草稿纸上的推演

请拿出草稿纸,我们尝试两种思维方式:

1. 模拟思维(适合初学者)

  • 动作:建立一个队列,不断弹出队首,让其票数减 1。
  • 判断:如果票数还没减到 0,就重新压入队尾。
  • 计数:每次弹出动作,时间 T=T+1T = T + 1
  • 终止:当第 kk 个人的票数变成 0 时,当前的 TT 就是答案。

2. 贡献法思维(数学优化,竞赛常用)

我们要算第 kk 个人买完票时总共过去了多少秒。这就等于:所有人在此期间一共买了几张票? 假设目标人物 kk 需要买 V=tickets[k]V = tickets[k] 张票:

  • 对于排在 kk 前面(或就是 kk 自己)的人 iki \le k: 在 kk 买完 VV 张票之前,这些人最多能买到几张? 答:如果他需要的票比 VV 多,他会买 VV 张;如果比 VV 少,他会买完自己的票就走。 即贡献时间为:min(tickets[i], V)
  • 对于排在 kk 后面的人 i>ki > k: 在 kk 买完第 VV 张票的那一刻,排在后面的人还没来得及买第 VV 轮票。 即贡献时间为:min(tickets[i], V - 1)

四、 复杂度分析与优化建议

  • 时间复杂度
    • 模拟法O(n×max(tickets))O(n \times \max(tickets))。本题数据范围 100×100=104100 \times 100 = 10^4,完全可以通过。
    • 计算法O(n)O(n)。只需遍历一遍数组,是这类题目的最优解。
  • 空间复杂度
    • 只需存储数组,空间复杂度 O(n)O(n)

优化建议: 在 NOI 考场上,若 nn 达到 10510^5,模拟法会超时(TLE),必须使用贡献法 O(n)O(n) 解决。


五、 算法流程图(伪代码逻辑)

为了符合 C++14 竞赛风格且避免 Mermaid 渲染错误,我们使用文字描述节点逻辑:

graph TD
    Start(开始处理任务) --> Input(读入 n, k 和 tickets 数组)
    Input --> Init(初始化 totalTime 等于 0, 目标票数 V 等于 tickets_k)
    Init --> LoopStart{遍历 i 从 0 到 n 减 1}
    LoopStart -- 遍历结束 --> Output(输出 totalTime)
    LoopStart -- 正在遍历 i --> Judge{判断 i 与 k 的位置关系}
    
    Judge -- i 小于或等于 k --> CalcA(该人买票数等于 tickets_i 与 V 的较小值)
    Judge -- i 大于 k --> CalcB(该人买票数等于 tickets_i 与 V 减 1 的较小值)
    
    CalcA --> Accumulate(totalTime 累加该买票数)
    CalcB --> Accumulate
    Accumulate --> LoopStart
    Output --> End(结束)

六、 读题关键词总结

  1. “走回队伍最后面”:这是循环队列模拟队列的标志。
  2. “买完票就离开”:意味着该元素在模拟中将不再参与后续循环。
  3. “第 k 个人买完票”:这是终止条件,要注意第 kk 轮买票时,排在他后面的人还没开始。
  4. “总时间”:在模拟中每步消耗 1 秒,在计算法中则是所有贡献的加和。

教练寄语: 这道题是训练“分类讨论”思维的良药。在 OI 竞赛中,很多看似需要复杂模拟的题目,只要你能站在“贡献”的角度去思考,往往能化繁为简。去写出你的 C++ 代码吧!