题解:学而思编程 射击王
【题目来源】
学而思编程:射击王
【题目描述】
高桥最近正在练习射击。他参加了一个射击比赛,比赛内容是射击气球。
共有 \(n\) 个气球,编号 \(1\sim n\)。每个气球都有一个起始高度 \(h_i\),比赛开始后,每个气球的高度每秒会升高 \(s_i\)。高桥开始时先打掉一个气球,随后每一秒他可以射击一次,打掉一个气球,直到打掉所有的气球。每个气球的分数为当他打掉气球时,这个气球的高度。而最终的得分就是所有气球分数的最大值。
高桥想知道他的最终得分最小可能是多少。
【输入】
第一行为气球的个数 \(n\)
接下来 \(n\) 行,每行两个整数 \(h_i\) 和 \(s_i\),表示气球的起始高度和气球的升高速度。
【输出】
输出一个数,表示高桥能拿到的最小分数
【输入样例】
4
5 6
12 4
14 7
21 2
【输出样例】
23
【核心思想】
-
问题分析:给定 \(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\) 秒及之前被打掉。
-
算法选择:
- 整数二分:对最终得分(最大高度)进行二分,将最优化问题转化为判定问题
- 贪心调度:对于给定的 \(mid\),计算每个气球最晚可被打掉的时间,按最晚时间升序排序,检查是否存在合法的调度方案
-
关键步骤:
- 判定函数
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\) 取决于编号方式)
- 若存在 \(h_i > mid\),直接返回
- 二分查找:
- 范围 \([0, 10^{18}]\),取中点 \(mid = \lfloor \frac{l + r}{2} \rfloor\)
- 若
check(mid)为真:\(ans = mid\),\(r = mid - 1\)(尝试更小的得分) - 否则:\(l = mid + 1\)(需要更大的得分)
- 输出答案:\(ans\)
- 判定函数
-
时间/空间复杂度:
- 时间复杂度:\(O(\log(10^{18}) \cdot n \log n) = O(n \log n \cdot 60)\),二分约 \(60\) 次,每次排序 \(O(n \log n)\)
- 空间复杂度:\(O(n)\),存储气球信息和最晚时间数组
-
二分答案 + 贪心调度的核心思想:
- 最优化转判定:将"最小化最大高度"转化为"能否在最大高度 \(\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
浙公网安备 33010602011771号