1 条题解

  • 1
    @ 2025-12-3 21:32:03

    一、详细思路分析

    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. 步骤拆解

    1. 输入读取与预处理
      • 读入 n(刀数)和 m(魔物血量,对应题目h)。
      • 读入每把刀的 a[i](直接伤害)和 b[i](投掷伤害),同时记录最大直接伤害 ma
    2. 投掷伤害排序:对 b 数组降序排序,方便后续累加前i个最大投掷伤害。
    3. 枚举所有投掷次数i
      • 累加前i个最大投掷伤害 s(当前投掷i把的总伤害)。
      • s >= m:仅需i次投掷即可消灭魔物,更新最小次数。
      • s < m:需用最强直接攻击补剩余伤害,计算补充次数(向上取整),总次数=投掷次数i+补充次数,更新最小次数。
    4. 输出最小次数:遍历所有i后,得到的最小值即为答案。

    4. 关键细节说明

    • 向上取整公式(x + y - 1) / y → 计算补充伤害的次数时,剩余伤害 x = m - s,单次补充伤害 y = ma,即使剩余1点伤害也需1次攻击,用该公式避免浮点数运算,高效且准确。
    • 数据溢出处理h 可达 1e9n 可达 1e5,总次数可能达 1e9 级别,需用 long long 存储,故宏定义 intlong 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 10a=[3]b=[5]
    • 预处理:ma=3b 排序后为 [5]
    • 枚举i=1:
      • s=5 < 10,剩余伤害 10-5=5
      • 补充次数 (5+3-1)/3 = 7/3=2
      • 总次数 1+2=3 → 与样例输出一致

    样例2验证

    • 输入:2 10a=[3,2]b=[5,6]
    • 预处理:ma=3b 排序后为 [6,5]
    • 枚举i=1:s=6 <10,补充次数 (4+3-1)/3=2,总次数 3
    • 枚举i=2:s=6+5=11 >=10,总次数 2 → 更新为最小值,与样例输出一致

    样例3验证

    • 输入:4 1e9a=[2,2,2,2]b=[2,1e7,3e7,99999999]
    • 预处理:ma=2b 排序后为 [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) → 存储 ab 数组的空间,适配 n<=1e5 的数据范围。

    该思路通过「优先最大化投掷伤害+后续最强直接攻击补充」的最优策略,结合枚举所有可能的投掷次数,确保找到最小攻击次数,同时时间复杂度高效,适配题目数据规模。

    • 1

    信息

    ID
    3946
    时间
    1000ms
    内存
    128MiB
    难度
    7
    标签
    (无)
    递交数
    147
    已通过
    31
    上传者