题解:学而思编程 射击王

【题目来源】

学而思编程:射击王

【题目描述】

高桥最近正在练习射击。他参加了一个射击比赛,比赛内容是射击气球。

共有 \(n\) 个气球,编号 \(1\sim n\)。每个气球都有一个起始高度 \(h_i\),比赛开始后,每个气球的高度每秒会升高 \(s_i\)。高桥开始时先打掉一个气球,随后每一秒他可以射击一次,打掉一个气球,直到打掉所有的气球。每个气球的分数为当他打掉气球时,这个气球的高度。而最终的得分就是所有气球分数的最大值。

高桥想知道他的最终得分最小可能是多少。

【输入】

第一行为气球的个数 \(n\)

接下来 \(n\) 行,每行两个整数 \(h_i\)\(s_i\),表示气球的起始高度和气球的升高速度。

【输出】

输出一个数,表示高桥能拿到的最小分数

【输入样例】

4
5 6
12 4
14 7
21 2

【输出样例】

23

【核心思想】

  1. 问题分析:给定 \(n\) 个气球,每个气球起始高度为 \(h_i\),每秒升高 \(s_i\)。高桥第 \(0\) 秒打掉第一个气球,之后每秒可以打掉一个气球。打掉第 \(i\) 个气球时,其高度为 \(h_i + s_i \times t_i\)\(t_i\) 为等待秒数)。需要安排打气球顺序,使得所有气球分数(打掉时的高度)的最大值最小。关键观察是:若限定最大分数为 \(mid\),则每个气球必须在高度不超过 \(mid\) 时被击落,即必须在第 \(\lfloor \frac{mid - h_i}{s_i} \rfloor\) 秒及之前被打掉。

  2. 算法选择

    • 整数二分:对最终得分(最大高度)进行二分,将最优化问题转化为判定问题
    • 贪心调度:对于给定的 \(mid\),计算每个气球最晚可被打掉的时间,按最晚时间升序排序,检查是否存在合法的调度方案
  3. 关键步骤

    • 判定函数 check(mid)
      • 若存在 \(h_i > mid\),直接返回 false(该气球无论如何都会在 \(mid\) 之前超过限制)
      • 计算每个气球的最晚打掉时间:\(t_i = \lfloor \frac{mid - h_i}{s_i} \rfloor\)
      • \(t_i\) 按升序排序
      • 检查调度可行性:第 \(i\) 个气球(从 \(0\) 开始计数)必须在第 \(i\) 秒及之前打掉,即要求 \(t_i \geq i - 1\)(或 \(t_i \geq i\) 取决于编号方式)
    • 二分查找
      • 范围 \([0, 10^{18}]\),取中点 \(mid = \lfloor \frac{l + r}{2} \rfloor\)
      • check(mid) 为真:\(ans = mid\)\(r = mid - 1\)(尝试更小的得分)
      • 否则:\(l = mid + 1\)(需要更大的得分)
    • 输出答案\(ans\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(\log(10^{18}) \cdot n \log n) = O(n \log n \cdot 60)\),二分约 \(60\) 次,每次排序 \(O(n \log n)\)
    • 空间复杂度:\(O(n)\),存储气球信息和最晚时间数组
  5. 二分答案 + 贪心调度的核心思想

    • 最优化转判定:将"最小化最大高度"转化为"能否在最大高度 \(\leq mid\) 的限制下完成所有射击",利用单调性进行二分
    • 最晚截止时间:对于给定的 \(mid\),每个气球有一个"最晚可射击时间" \(t_i = \lfloor \frac{mid - h_i}{s_i} \rfloor\),超过该时间则高度超过 \(mid\)
    • 贪心排序策略:按最晚时间升序排序后,优先射击截止时间早的气球(最早截止时间优先,EDF 策略)。这是调度问题的经典贪心策略,可证明其最优性——若存在可行调度,则按截止时间排序后顺序执行必可行
    • 可行性判定:排序后检查第 \(i\) 个气球的截止时间是否 \(\geq\) 其被分配的时间槽(第 \(i-1\) 秒),若全部满足则调度可行
    • 适用于带截止时间约束的调度问题、最小化最大完成时间等场景

【解题思路】

【算法标签】

整数二分

【代码详解】

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5 + 5;

int n, h[N], s[N];
LL t[N];

// 检查是否能在mid时间内完成任务
bool check(LL mid)
{
    // cout << "mid " << mid << endl;  // 调试用

    for (int i = 1; i <= n; i++) {
        // 如果mid时间小于初始高度,肯定完不成
        if (mid < h[i]) return false;

        // 计算在mid时间内,第i个任务最晚可以开始的时间
        t[i] = (mid - h[i]) / s[i];
    }

    // 按照最晚开始时间排序
    sort(t + 1, t + n + 1);

    // 检查是否能合理安排任务顺序
    for (int i = 1; i <= n; i++) {
        // 第i个任务必须在时间i-1之前开始
        if (t[i] < i - 1) return false;
    }
    return true;
}

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        scanf("%d%d", &h[i], &s[i]);

    // 二分查找最小时间
    LL l = 0, r = 1e18;  // 时间范围
    LL ans = 0;

    while (l <= r) {
        LL mid = (l + r) / 2;
        if (check(mid)) {  // 如果mid时间可行
            ans = mid;  // 记录答案
            r = mid - 1;  // 尝试更小的时间
        } else {
            l = mid + 1;  // 需要更多时间
        }
    }

    printf("%lld\n", ans);
    return 0;
}

【运行结果】

4
5 6
12 4
14 7
21 2
23
posted @ 2026-07-02 22:45  团爸讲算法  阅读(4)  评论(0)    收藏  举报