题解:洛谷 P9124 Bakery

【题目来源】

洛谷:P9124 [USACO23FEB] Bakery S - 洛谷

【题目描述】

Bessie 开了一家面包店!

在她的面包店里,Bessie 有一个烤箱,可以在 \(t_C\) 的时间内生产一块饼干或在 \(t_M\)单位时间内生产一块松糕。\((1\le t_C,t_M\le 10^9)\)。由于空间限制,Bessie 一次只能生产一种糕点,所以要生产 \(A\) 块饼干和 \(B\) 块松饼,需要 \(A\cdot t_C+B\cdot t_M\)单位的时间。

Bessie的 \(N(1\le N\le 100)\)朋友都想一个一个地去面包店。第 \(i\) 个朋友一进门就会点 \(a_i(1\le a_i\le 10^9)\)块饼干和 \(b_i(1\le b_i\le 10^9)\)块松饼。Bessie 没有空间来储存糕点,所以她只有在接到订单后才开始制作糕点。此外,Bessie 的朋友都很忙,所以第 \(i\) 个朋友只愿意等 \(c_i(a_i+b_i\le c_i\le 2\cdot 10^{18})\)个单位的时间,然后就伤心地离开。

Bessie 真的不希望她的朋友们伤心,她可以用一块钱升级她的烤箱,让它少花一个单位的时间来生产一块饼干或少花一个单位的时间来生产一个松饼。她不能将她的烤箱升级到花费小于等于 \(0\) 的时间,但她可以选择在她的朋友到来之前将她的烤箱升级多少次,只要生产一块饼干和生产一个松饼所需的时间都严格保持为正数。

对于每一个 \(T(1\le T\le 100)\) 的测试案例,请帮助 Bessie 找出她必须花费的最小的钱数量,以便她的面包店能够满足所有的朋友。

【输入】

第一行包含 \(T\),测试案例的数量。

每个测试用例都以一行开始,包含 \(N,t_C,t_M\)。然后,接下来的 \(N\) 行各包含三个整数 \(a_i,b_i,c_i\)

测试案例用换行符隔开。

【输出】

Bessie 需要为每个测试案例花费的最少钱数,每行一个。

【输入样例】

2

3 7 9
4 3 18
2 4 19
1 1 6

5 7 3
5 9 45
5 2 31
6 4 28
4 1 8
5 2 22

【输出样例】

11
6

【核心思想】

  1. 问题分析:给定烤箱初始生产饼干时间 \(t_C\) 和松饼时间 \(t_M\)\(N\) 个朋友各需 \(a_i\) 块饼干和 \(b_i\) 块松饼,最多等待 \(c_i\) 时间。每次升级花费 \(1\) 元,可减少 \(1\) 单位饼干或松饼的生产时间(但不能 \(\leq 0\))。求满足所有朋友的最小花费。本质上是整数二分 + 约束分析问题:设总升级次数为 \(t\),分配给饼干 \(x\) 次、松饼 \(t-x\) 次,转化为验证是否存在合法的 \(x\) 使得所有订单满足。

  2. 算法选择

    • 二分答案:对总升级次数 \(t\) 进行二分,\(t \in [0, t_C + t_M - 2]\)(保证两种产品时间均 \(\geq 1\)
    • 约束推导:设升级后饼干时间为 \(t_C - x\),松饼时间为 \(t_M - (t-x)\),第 \(i\) 个朋友的约束为 \(a_i(t_C-x) + b_i(t_M-t+x) \leq c_i\),整理得关于 \(x\) 的不等式
  3. 关键步骤

    • 读取数据:读入 \(T\) 组数据,每组读入 \(N, t_C, t_M\)\(N\) 个订单 \((a_i, b_i, c_i)\)
    • 二分总升级次数 \(t\)
      • \(l = 0\)\(r = t_C + t_M - 2\)\(ans = r\)
      • \(l \leq r\)\(mid = (l + r) / 2\)
        • check(mid) 为真,\(ans = mid\)\(r = mid - 1\)
        • 否则 \(l = mid + 1\)
    • check(t) 函数
      • 计算 \(x\) 的范围:\(lower = \max(0, t - t_M + 1)\)\(upper = \min(t, t_C - 1)\)
      • 对每个朋友 \(i\)
        • \(m = a_i \cdot t_C + b_i \cdot t_M - c_i\)(需要节省的总时间)
        • \(m \leq 0\),该朋友自动满足
        • \(a_i = b_i\):需要 \(a_i \cdot t \geq m\),否则返回 false
        • \(a_i > b_i\):推导 \(x \geq \lceil \frac{m - b_i \cdot t}{a_i - b_i} \rceil\),更新 \(lower\)
        • \(a_i < b_i\):推导 \(x \leq \lfloor \frac{m - b_i \cdot t}{a_i - b_i} \rfloor\),更新 \(upper\)
        • \(lower > upper\),返回 false
      • 返回 true
    • 输出 \(ans\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(T \cdot \log(t_C + t_M) \cdot N)\),二分 \(\log\) 次,每次验证 \(O(N)\)
    • 空间复杂度:\(O(N)\),存储订单信息
  5. 整数二分 + 线性约束的核心思想

    • 总升级次数的单调性:若总升级 \(t\) 可满足,则 \(t+1\) 也一定可满足(多升级不会更差),满足二分条件
    • 变量替换:设分配给饼干的升级次数为 \(x\),则松饼为 \(t-x\),将二维优化转化为一维约束
    • 不等式分类讨论:根据 \(a_i\)\(b_i\) 的大小关系,\(x\) 的约束方向不同(下界或上界),需分别处理
    • 整数边界处理:向上取整和向下取整的精确计算,避免浮点数精度问题
    • 适用于资源分配、参数优化、约束满足类问题

【解题思路】

【算法标签】

普及+ #整数二分

【代码详解】

#include <bits/stdc++.h>
using namespace std;
#define int long long  // 将所有变量类型都改为long long
int T, tc, tM, n;
struct node {
    int a, b, c;
}p[105];
bool check(int t)
{
    int lower = max(0LL, t-tM+1);  //公式6
    int upper = min(t, tc-1);  // 公式4
    for (int i=1; i<=n; i++) {  // 遍历所有朋友
        if (lower>upper) {  // 如果lower大于upper,不成立
            return false;
        }
        int a = p[i].a, b = p[i].b;
        int m = a*tc + b*tM - p[i].c;  // 计算需要增加钱
        if (m<=0) continue;  // 如果不需要升级,继续循环
        if (a==b) {  // 分三种情况讨论
            if (a*t<m) return false;  // 需要at>=m
        } else if (a>b) {
            int x = (m-b*t)/(a-b);  // 计算x
            if ((m-b*t) % (a-b) && (m-b*t)*1.0/(a-b)>0) {  // 如果可以整除,且可能是小数
                x++;  // 则自增1
            }
            if (x>upper) return false;  // 如果x大于上限,不成立
            lower = max(lower, x);  // 提高下限
        } else {
            int x = (m-b*t)/(a-b);  // 计算x
            if ((m-b*t) % (a-b) && (m-b*t)*1.0/(a-b)<0) {  // 如果可以整除,且可能是小数
                x--;  // 因为是负数,所以要自减1
            }
            if (x<lower) return false;  // 如果x小于下限,不成立
            upper = min(upper, x);  // 降低上限
        }
    }
    return true;
}
signed main()  //前面#define,所以这里需要改为signed
{
    ios::sync_with_stdio(false);  // 优化cin速度
    cin.tie(NULL);
    cin >> T;  // 输入T
    while (T--) {  // 遍历T组数据
        cin >> n >> tc >> tM;  // 输入n、tc和tm
        for (int i=1; i<=n; i++) {  // 记录所有人的饼干、松饼和最大等待时间
            cin >> p[i].a >> p[i].b >> p[i].c;
        }
        int l=0, r = tc+tM-2, ans=r;  // 使用二分搜索,因为至少要1块饼干和1块松饼,所以r为tc+tm-2
        while (l<=r) {  // 二分答案模板
            int mid = (l+r)>>1;
            if (check(mid)) {
                ans = mid;
                r = mid-1;  // 如果mid可以满足要求,那就减少的再少一点(题目要求求最小值)
            } else {
                l = mid+1;  // 否则,那就减少的再多点
            }
        }
        cout << ans << endl;  // 输出结果
    }

    return 0;
}

【运行结果】

2

3 7 9
4 3 18
2 4 19
1 1 6
11

5 7 3
5 9 45
5 2 31
6 4 28
4 1 8
5 2 22
6
posted @ 2026-08-17 22:48  团爸讲算法  阅读(10)  评论(0)    收藏  举报