蓝桥杯备赛:Day4-P10387 训练士兵

📚 算法笔记:P10387 训练士兵 (贪心与代价权衡)

1. 题目简述

P10387 [蓝桥杯 2024 省 A 训练士兵 - 洛谷](https://www.luogu.com.cn/problem/P10387)

有 n n n 个士兵需要进行训练,每个士兵有一个单独训练的费用 p i p_i pi 和需要达标的训练次数 c i c_i ci。

同时存在一种"团购"方案:花费 S S S 元可以让所有当前还没达标的士兵统一训练一次。

目标:求出让所有士兵都达标的最小总花费。

2. 核心代码 (C++ AC版本)

c++ 复制代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

struct Soldier 
{
    ll price, cnt; 
};

// 贪心核心:按需求次数从【小】到【大】排序,模拟时间线的流逝
bool cmp(Soldier a, Soldier b) 
{
    return a.cnt < b.cnt;
}

void solve() 
{
    ll people, Sum;
    if (!(cin >> people >> Sum)) return;
    
    vector<Soldier> queue(people);
    ll total_p = 0; // 记录当前所有未毕业士兵的单练总价
    
    for (int i = 0; i < people; i++) 
    {
        cin >> queue[i].price >> queue[i].cnt;
        total_p += queue[i].price; 
    }
    
    // 按时间线(需求次数)排序
    sort(queue.begin(), queue.end(), cmp);
    
    ll ans = 0;
    ll last_train = 0; // 记录已经流逝的时间(已经统一训练了多少次)
    
    for (int i = 0; i < people; i++) 
    {
        // 当前这个士兵还需要练几次才能毕业?
        ll rounds = queue[i].cnt - last_train;
        
        if (rounds > 0) 
        {
            // 在这 rounds 次训练中,每次都选择当前最便宜的方案(团购 or 全员单练)
            ans += rounds * min(total_p, Sum);
            // 时间线推进
            last_train = queue[i].cnt; 
        }
        
        // 核心交接:当前士兵毕业,退出训练群。以后的单练总价不再包含他
        total_p -= queue[i].price;
    }
    
    cout << ans << "\n";
}

int main() 
{
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    
    int _ = 1;
    while (_--) 
    {
        solve();
    }
    return 0;
}

3. 核心考点与注意事项

🔍 核心考点
  1. 代价权衡 (Trade-off) 的贪心思维 :在每一个时间切片里,我们面临两个选择:要么花 S S S 元团购,要么花 total_p 元单练。贪心策略就是:每一轮都取 min ⁡ ( t o t a l _ p , S ) \min(total\_p, S) min(total_p,S)。
  2. 时间线模拟(横向切片法) :不要盯着"一个人"看他要怎么练,而是要盯着"时间线"看。随着时间推移,不断有人达到 c i c_i ci 次要求并"毕业"。
  3. 动态维护成本 :当一个士兵毕业时,他不再参与后续的单练,因此要在 total_p 中减去他的 p i p_i pi。一旦 total_p 跌破团购价 S S S,后续的所有训练将自动切换为单练模式。
⚠️ 注意事项
  • 数据类型 :费用 × \times × 次数极易爆 int,必须全线使用 long long。
  • 时空复杂度 :使用 sort 排序时间复杂度为 O ( N log ⁡ N ) O(N \log N) O(NlogN),遍历一次 O ( N ) O(N) O(N),总复杂度 O ( N log ⁡ N ) O(N \log N) O(NlogN),完美契合 N = 10 5 N=10^5 N=105 的数据范围。

4. 易错点回顾 (My Mistakes)

  1. 致命的逻辑错误:排序方向反了 (WA)
    • 错误思路 :起初按需求次数从大到小排序,直接给需求最大的人买满团购。
    • 产生后果 :忽略了在漫长的训练过程中,需求少的人会提前毕业。他们毕业后,剩下的单练总价可能早就比团购便宜了。
    • 纠正方案 :必须从小到大排序。模拟时间流逝,"剥洋葱"式地一层一层结算,有人毕业就实时扣除他的单练费用。
  2. std::vector 的越界访问 (RE 隐患)
    • 错误写法 :定义了 vector<Soldier> queue(people),循环却写成 for(int i = 1; i <= people; i++)。
    • 产生后果 :vector 的默认索引是从 0 0 0 到 s i z e − 1 size-1 size−1。从 1 1 1 开始赋值会导致第 0 0 0 个元素为空(值为 0 0 0),且访问最后一个元素时发生数组越界,最终被 sort 打乱导致答案全错。
    • 纠正方案 :使用 vector 时,必须形成肌肉记忆,永远从 0 0 0 开始遍历 (i = 0; i < n; i++)。
相关推荐
C^h4 小时前
c++ string
开发语言·c++
raindayinrain4 小时前
c++高性能--原子操作,无锁编程,操作顺序
开发语言·c++·性能优化
学逆向的4 小时前
C++ 合并命名空间
开发语言·c++·网络安全
Zhao-Jichao5 小时前
VS2026 打包 C++ 程序生成 EXE,借助 Installer Project 扩展工具
开发语言·c++
小徐不徐说6 小时前
C++ 接口与插件设计:从工厂注册到 Qt 动态插件
数据库·c++·qt·系统架构·程序设计
大都督会赢的6 小时前
C++第一节课
开发语言·c++
UIU1146 小时前
P1233 [ICPC 2001 Taejon R] 木棍加工
数据结构·c++·算法·动态规划
TMedical7 小时前
MedPlanning 截骨模拟矫形功能
c++·图像处理·人工智能·qt·健康医疗
qq_406981037 小时前
共享变量一 关于extern与const
开发语言·c++
chari克里9 小时前
QT5.15.2 + msvc2022部署全流程说明(windows可联网环境)
开发语言·c++·qt