1 条题解
-
1
一、详细思路分析
1. 核心问题拆解与关键观察
- 攻击方式特性:
- 直接攻击:无限使用,单次伤害
a[i],但b[i] >= a[i](题目保证)→ 投掷攻击单次伤害必然不低于直接攻击,优先用投掷更划算。 - 投掷攻击:仅能使用1次,单次伤害
b[i],使用后消失 → 要选伤害最大的投掷,才能最大化单次收益。
- 直接攻击:无限使用,单次伤害
- 目标:最小化攻击次数 → 优先用「高伤害的投掷攻击」,剩余伤害用「最强的直接攻击」补充(直接攻击中选最大
a[i],单次伤害最高,补伤害次数最少)。
2. 最优策略设计
- 策略1:投掷攻击选「伤害最大的前i把」→ 对
b数组降序排序,累加前i个b值(最大的i个投掷伤害),确保投掷阶段伤害最大化。 - 策略2:补充伤害选「最强直接攻击」→ 提前找到所有
a[i]中的最大值ma,后续补伤害时用ma,单次伤害最高,补充次数最少。 - 策略3:枚举所有可能的投掷次数
i(1~n)→ 因为无法直接判断投多少把最优(可能投1把+补几次,或投2把更优),所以遍历所有i,计算每种情况下的总次数,取最小值。
3. 步骤拆解
- 输入读取与预处理:
- 读入
n(刀数)和m(魔物血量,对应题目h)。 - 读入每把刀的
a[i](直接伤害)和b[i](投掷伤害),同时记录最大直接伤害ma。
- 读入
- 投掷伤害排序:对
b数组降序排序,方便后续累加前i个最大投掷伤害。 - 枚举所有投掷次数i:
- 累加前i个最大投掷伤害
s(当前投掷i把的总伤害)。 - 若
s >= m:仅需i次投掷即可消灭魔物,更新最小次数。 - 若
s < m:需用最强直接攻击补剩余伤害,计算补充次数(向上取整),总次数=投掷次数i+补充次数,更新最小次数。
- 累加前i个最大投掷伤害
- 输出最小次数:遍历所有i后,得到的最小值即为答案。
4. 关键细节说明
- 向上取整公式:
(x + y - 1) / y→ 计算补充伤害的次数时,剩余伤害x = m - s,单次补充伤害y = ma,即使剩余1点伤害也需1次攻击,用该公式避免浮点数运算,高效且准确。 - 数据溢出处理:
h可达1e9,n可达1e5,总次数可能达1e9级别,需用long long存储,故宏定义int为long long。 - 枚举i的必要性:例如样例2,投2把刚好够;样例1投1把不够,补2次直接攻击;样例3投3把后补大量直接攻击,只有遍历所有i才能找到最优解。
二、带详细注释的代码
#include<bits/stdc++.h> #define int long long // 宏定义:将int替换为long long,避免数据溢出(h可达1e9,总次数可能超int范围) using namespace std; signed main(){ // 变量定义与初始化 int n; // 刀的总数(对应题目输入n) int m; // 魔物的血量(对应题目输入h,变量名简化为m) int ma = 0; // 所有刀中最大的直接攻击伤害(后续补伤害的最优选择) int a[100010]; // 存储每把刀的直接攻击伤害(a[i]:第i把刀直接攻击的伤害值) int b[100010]; // 存储每把刀的投掷攻击伤害(b[i]:第i把刀投掷攻击的伤害值) int ans = 1e9; // 记录最少攻击次数,初始设为极大值(1e9),用于后续取最小值对比 int s = 0; // 累加变量:存储前i把最大投掷伤害的总和(排序后累加前i个b值) // 第一步:读入核心输入数据 cin >> n >> m; // 第二步:读入每把刀的攻击数据,同时找到最大直接攻击伤害ma for (int i = 1; i <= n; i++){ cin >> a[i] >> b[i]; // 读第i把刀的直接伤害a[i]和投掷伤害b[i] ma = max(ma, a[i]); // 实时更新最大直接攻击伤害:后续补伤害用最强直接攻击,次数最少 } // 第三步:对投掷伤害数组b降序排序(关键操作) // 原因:b[i] >= a[i],投掷攻击单次伤害更高,优先选伤害最大的投掷,最大化单次收益 sort(b + 1, b + 1 + n, greater<int>()); // 第四步:枚举所有可能的投掷次数i(i从1到n,尝试投1把、2把...n把的所有情况) for (int i = 1; i <= n; i++){ s += b[i]; // 累加前i把最大的投掷伤害(当前投掷i把刀的总伤害) if (s >= m){ // 情况1:投掷i把刀的总伤害已足够消灭魔物(无需后续直接攻击) ans = min(ans, i); // 更新最少次数:当前i次(仅投掷)与历史最小值比较 } else { // 情况2:投掷i把刀后伤害仍不足,需用最强直接攻击补剩余伤害 int remain_hp = m - s; // 剩余需要的伤害:魔物总血量 - 已投掷的总伤害 // 补充次数 = 剩余伤害 / 最大直接攻击伤害(向上取整) // 向上取整公式:(x + y - 1) / y → 等价于ceil(x/y),避免浮点数运算 int supplement_times = (remain_hp + ma - 1) / ma; // 总次数 = 投掷次数i + 补充次数,与历史最小值比较更新 ans = min(ans, i + supplement_times); } } // 第五步:输出最少攻击次数 cout << ans << endl; return 0; }三、关键逻辑对应样例验证
样例1验证
- 输入:
1 10,a=[3],b=[5] - 预处理:
ma=3,b排序后为[5] - 枚举i=1:
s=5 < 10,剩余伤害10-5=5- 补充次数
(5+3-1)/3 = 7/3=2 - 总次数
1+2=3→ 与样例输出一致
样例2验证
- 输入:
2 10,a=[3,2],b=[5,6] - 预处理:
ma=3,b排序后为[6,5] - 枚举i=1:
s=6 <10,补充次数(4+3-1)/3=2,总次数3 - 枚举i=2:
s=6+5=11 >=10,总次数2→ 更新为最小值,与样例输出一致
样例3验证
- 输入:
4 1e9,a=[2,2,2,2],b=[2,1e7,3e7,99999999] - 预处理:
ma=2,b排序后为[99999999,3e7,1e7,2] - 枚举i=3:
s=99999999+3e7+1e7=139999999 <1e9- 剩余伤害
1e9 -139999999=860000001 - 补充次数
(860000001 +2-1)/2=860000002/2=430000001 - 总次数
3+430000001=430000004→ 与样例输出一致
四、复杂度分析
- 时间复杂度:
O(n log n)→ 核心是对b数组的排序(O(n log n)),枚举i的循环为O(n),整体由排序主导。 - 空间复杂度:
O(n)→ 存储a和b数组的空间,适配n<=1e5的数据范围。
该思路通过「优先最大化投掷伤害+后续最强直接攻击补充」的最优策略,结合枚举所有可能的投掷次数,确保找到最小攻击次数,同时时间复杂度高效,适配题目数据规模。
- 攻击方式特性:
- 1
信息
- ID
- 3946
- 时间
- 1000ms
- 内存
- 128MiB
- 难度
- 7
- 标签
- (无)
- 递交数
- 147
- 已通过
- 31
- 上传者