【算法刷题】蓝桥杯:怎么倒茶(贪心算法 + 结构体排序)
📌 题目链接与描述
- 题目名称:怎么倒茶
- 题目链接 :蓝桥云课 - 怎么倒茶
- 数据规模 : 1 ≤ n ≤ 10 4 1 \le n \le 10^4 1≤n≤104, 1 ≤ a i ≤ b i ≤ 10 6 1 \le a_i \le b_i \le 10^6 1≤ai≤bi≤106, ∑ a i ≤ W ≤ 10 9 \sum a_i \le W \le 10^9 ∑ai≤W≤109, 1 ≤ s i ≤ 10 6 1 \le s_i \le 10^6 1≤si≤106
- 核心问题 :有 W W W 毫升茶水分配给 n n n 位领导。第 i i i 位领导的倒茶量 x i x_i xi 须满足 a i ≤ x i ≤ b i a_i \le x_i \le b_i ai≤xi≤bi,产生的开心度为 s i × x i s_i \times x_i si×xi。求所有领导开心度之和的最大值。
💡 解题核心思路:贪心算法(Greedy)
为了使总开心度最大,我们可以将倒茶过程分为两个阶段:
1. 基础保底分配(满足硬性下限)
- 每个人必须至少 得到 a i a_i ai 毫升茶。
- 先给所有人各倒 a i a_i ai 毫升茶,扣除后茶壶剩余茶量为: left_w = W − ∑ a i \text{left\_w} = W - \sum a_i left_w=W−∑ai。
- 此时获得的基础开心度为: min_s = ∑ ( a i × s i ) \text{min\_s} = \sum (a_i \times s_i) min_s=∑(ai×si)。
2. 贪心追加分配(追求最大收益)
- 给完下限后,第 i i i 位领导最多还能接受 ( b i − a i ) (b_i - a_i) (bi−ai) 毫升茶。
- 每多给 1 毫升茶带来的额外收益是 s i s_i si,因此 s i s_i si 越大,性价比越高。
- 策略 :按开心度权值 s i s_i si 从大到小排序 。优先把剩余茶量 left_w \text{left\_w} left_w 分配给 s i s_i si 最高的领导,直到填满其上限 b i b_i bi 或茶水用完。
❌ 常见踩坑与易错点
vector未初始化长度(导致段错误/运行错误) :- 错 :
vector<Person> p; cin >> p[i].a;(访问越界崩溃)。 - 对 :
vector<Person> p(n);明确指定大小。
- 错 :
- 变量名与 C++ 标准库冲突 :
- 在使用
using namespace std;时,不要声明变量名为left(会与std::left冲突导致编译错误),建议使用left_w或rest_w。
- 在使用
- 数据类型溢出 :
- 茶水量 W ≤ 10 9 W \le 10^9 W≤109,开心度权值 s i ≤ 10 6 s_i \le 10^6 si≤106,累加的总开心度可达 10 15 10^{15} 1015 级别,必须全部使用
long long。
- 茶水量 W ≤ 10 9 W \le 10^9 W≤109,开心度权值 s i ≤ 10 6 s_i \le 10^6 si≤106,累加的总开心度可达 10 15 10^{15} 1015 级别,必须全部使用
💻 最终 AC 代码 (C++)
cpp
#include <bits/stdc++.h>
using namespace std;
struct Person {
long long a, b, s;
};
// 按照开心度权值 s 从大到小排序
bool cmp(Person a, Person b) {
return a.s > b.s;
}
int main() {
// 快速 I/O
ios::sync_with_stdio(false);
cin.tie(nullptr);
long long n, w;
long long min_s = 0;
if (!(cin >> n >> w)) return 0;
vector<Person> p(n); // 正确分配 n 个元素的空间
long long min_a = 0;
for (int i = 0; i < n; i++) {
cin >> p[i].a >> p[i].b >> p[i].s;
min_a += p[i].a; // 累加必须分配的下限
min_s += p[i].a * p[i].s; // 累加基础开心度
}
long long left_w = w - min_a; // 剩余可自由分配的茶量
// 按权值降序排序
sort(p.begin(), p.end(), cmp);
// 贪心追加
for (int i = 0; i < n && left_w > 0; i++) {
long long temp = min(p[i].b - p[i].a, left_w); // 实际能给的量
left_w -= temp;
min_s += temp * p[i].s;
}
cout << min_s << "\n";
return 0;
}
⏱️ 复杂度分析
- 时间复杂度 : O ( n log n ) \mathcal{O}(n \log n) O(nlogn)
- 读入数据 :需要 O ( n ) \mathcal{O}(n) O(n)。
- 排序瓶颈 :对 n n n 个领导按开心度权值 s i s_i si 排序,耗时 O ( n log n ) \mathcal{O}(n \log n) O(nlogn)。
- 遍历追加 :分配剩余茶水仅需 O ( n ) \mathcal{O}(n) O(n)。
- 总体评估 :当 n = 10 4 n = 10^4 n=104 时,总计算量仅为 10 5 10^5 105 次左右,实际运行时间在 5ms 内,轻松 AC。
- 空间复杂度 : O ( n ) \mathcal{O}(n) O(n),主要用于存储 n n n 个结构体对象的
vector。