本文最后更新于 9 个月前,文中所描述的信息可能已发生改变。
F. Final Boss 解题思路
核心问题分析与数学转化
本题要求找到击败生命值为 的 Boss 所需的最少回合数 。我们有 种攻击,第 种攻击伤害为 ,冷却时间为 。在回合 时,该攻击能够被使用的次数 决定了其贡献的总伤害。
1. 攻击次数与回合数的关系
一个关键的观察是,第 种攻击在回合 时可以使用。在给定回合数 时,其使用次数 的计算公式如下:
该攻击在 回合内能被使用的次数 为:
因此,在 个回合内,总共对 Boss 造成的伤害 为所有攻击贡献的累加和:
我们的核心任务,便转化成了找到满足 的最小正整数回合数 。
2. 单调性与二分查找优化
由于 均为正数,回合数 越大, 必定是单调递增的(非递减)。
- Boss 血量 和攻击参数 最大值约为 。
- 答案 的范围可能非常大,根据示例和 的上界估算, 接近 或更高。线性查找 是不可行的。
这种单调性使得我们可以利用二分查找来寻找满足条件的最小 ,将 的时间复杂度降低至 。
核心优化:二分查找与溢出防护
递推公式的建立 (Check 函数)
我们定义 函数来计算 回合内的总伤害并与 比较。
- 查找范围: 。我们将 ,并设定一个足够大的安全上界 ,确保包含所有可能的答案。
- 溢出防护: 由于总伤害 可能超过 $ 64$ 位整数 (
long long) 的上限 ($ 9 \times 10^{18}$),在count_damage函数内部,必须对乘法 和累加 进行溢出检查。如果发生溢出,则表明伤害已达到极大值,足以击败 Boss。
核心递推关系:
设 为二分查找的中间值。
如果 ,则答案可能为 或更小;否则,答案在 区间。
CPP 代码实现
代码中使用了 SAFE_LARGE_DAMAGE 常量作为溢出标记,并使用标准二分查找模板寻找满足条件的左边界(最小值)。
cpp
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
// 安全的溢出上界 (约 9 * 10^18)。如果总伤害超过此值,认为必定能击败 Boss。
constexpr ll SAFE_LARGE_DAMAGE = 9000000000000000000LL;
ll count_damage(ll R, const vector<ll>& damages, const vector<ll>& cooldowns) {
ll total_damage = 0;
size_t N = damages.size();
for (size_t i = 0; i < N; ++i) {
// 计算攻击 i 的使用次数 K_i = floor((R - 1) / c_i) + 1
ll uses = (R - 1) / cooldowns[i] + 1;
// 检查乘法溢出:a_i * uses
if (damages[i] > 0 && uses > LLONG_MAX / damages[i]) {
return SAFE_LARGE_DAMAGE;
}
ll damage_contribution = damages[i] * uses;
// 检查累加溢出:total_damage + damage_contribution
if (total_damage > LLONG_MAX - damage_contribution) {
return SAFE_LARGE_DAMAGE;
}
total_damage += damage_contribution;
// 如果 total_damage 已经超过 SAFE_LARGE_DAMAGE,提前返回
if (total_damage >= SAFE_LARGE_DAMAGE) {
return SAFE_LARGE_DAMAGE;
}
}
return total_damage;
}
void solve() {
ll H, N;
// H: Boss血量,N: 攻击种类数
cin >> H >> N;
vector<ll> damages(N);
vector<ll> cooldowns(N);
// 读取伤害 A_i
for (int i = 0; i < N; ++i) {
cin >> damages[i];
}
// 读取冷却 C_i
for (int i = 0; i < N; ++i) {
cin >> cooldowns[i];
}
// 二分查找范围
ll start = 1;
// 设定一个足够大的上界,保守取 2 * 10^14
ll end = 200000000000000LL;
ll ans = end;
// 定义检查函数 check(R): 总伤害是否 >= H
auto check_data = [&](ll R) -> bool {
return count_damage(R, damages, cooldowns) >= H;
};
// 标准二分查找 (查找满足条件的最小左边界)
while (start <= end) {
ll mid = start + (end - start) / 2;
if (check_data(mid)) {
// mid 可以击败 Boss,记录 ans 并尝试更小的 R
ans = mid;
end = mid - 1;
} else {
// mid 不足以击败 Boss,需要更多的回合
start = mid + 1;
}
}
cout << ans << "\n";
}
int main() {
// 优化 I/O 速度
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
}