【算法刷题】蓝桥杯:怎么倒茶(贪心算法 + 结构体排序)

【算法刷题】蓝桥杯:怎么倒茶(贪心算法 + 结构体排序)

📌 题目链接与描述

  • 题目名称:怎么倒茶
  • 题目链接蓝桥云课 - 怎么倒茶
  • 数据规模 : 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 或茶水用完。

❌ 常见踩坑与易错点

  1. vector 未初始化长度(导致段错误/运行错误)
    • vector<Person> p; cin >> p[i].a;(访问越界崩溃)。
    • vector<Person> p(n); 明确指定大小。
  2. 变量名与 C++ 标准库冲突
    • 在使用 using namespace std; 时,不要声明变量名为 left(会与 std::left 冲突导致编译错误),建议使用 left_wrest_w
  3. 数据类型溢出
    • 茶水量 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

💻 最终 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
相关推荐
OuO-22 小时前
笔试强训 Day 43:kotori 和抽卡(二)、ruby 和薯条、循环汉诺塔
数学·前缀和·二分查找·动态规划·排序·滑动窗口·循环汉诺塔
Tisfy5 天前
LeetCode 3731.找出缺失的元素:哈希 / 排序
算法·leetcode·哈希算法·排序·哈希表
Tisfy14 天前
LeetCode 3016.输入单词需要的最少按键次数 II:排序(贪心)
linux·算法·leetcode·题解·贪心·排序·计数
Tisfy15 天前
LeetCode 3014.输入单词需要的最少按键次数 I:遍历 / if-else计算(比纯数学公式写起来麻烦但好想)
数学·算法·leetcode·字符串·题解·贪心
Tisfy17 天前
LeetCode 3517.最小回文排列 I:排序(Python两行版) / 计数(O(n)时间+O(C)空间+字符串原地修改)
c语言·python·leetcode·字符串·排序·计数排序·回文
txzrxz19 天前
拓补排序讲解
c++·算法·图论·排序
qz5zwangzihan124 天前
题解:Atcoder Beginner Contest abc467 F - Email Scheduling Optimization
c++·贪心·atcoder·平衡树·abc467·abc467_f·fhq-treap
cpp_25011 个月前
P6625 [省选联考 2020 B 卷] 卡牌游戏
数据结构·c++·算法·前缀和·贪心·洛谷题解·省选
cpp_25011 个月前
P10098 [ROIR 2023] 地铁建设 (Day 2)
数据结构·c++·算法·贪心·二分答案·洛谷题解