蓝桥杯备赛: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 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 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 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++)`**。

相关推荐
江屿风10 分钟前
C++OJ题经验总结(竞赛)1
开发语言·c++·笔记·算法
运筹vivo@30 分钟前
LeetCode 2405. 子字符串的最优划分
c++·算法·leetcode·职场和发展·哈希表
有点。1 小时前
C++(枚举法一练习题)
开发语言·c++·算法
basketball6161 小时前
C++ 单例模式完全指南:从饿汉式到现代 C++ 的最佳实践
java·c++·单例模式
玖釉-2 小时前
栈——栈的定义及基本操作
c++·windows·算法·图形渲染
不想写代码的星星2 小时前
C++ 内存序六件套:从完全同步到爱咋咋地
c++
haibindev3 小时前
别让AI再从零写一堆优美的屎山了
c++·ai编程·claude·流媒体·codex·代码复用
Zhang~Ling3 小时前
C++ 模板初阶:从函数模板到类模板
c++
蜕变的土豆3 小时前
Visual Studio编译时,报错windows sdk 不匹配,找不到windows sdk
c++