This commit is contained in:
HuangHai
2025-09-21 14:58:18 +08:00
parent 51c15a5e24
commit 37fe77b479

View File

@@ -0,0 +1,125 @@
#include <bits/stdc++.h>
using namespace std;
// 定义长整型以防止整数溢出
typedef long long LL;
// 全局变量定义
int n, m; // n: 小A持有的课堂优秀券数量; m: 小A持有的作业优秀券数量
int a, b; // a: 第一种兑换方式所需的课堂优秀券数量; b: 第一种兑换方式所需的作业优秀券数量
int l, r; // l: 二分查找的左边界; r: 二分查找的右边界
/**
* 检查函数判断是否可以兑换v份奖品
*
* 参数:
* v: 尝试兑换的奖品数量
*
* 返回值:
* 1 (true): 可以兑换v份奖品
* 0 (false): 不能兑换v份奖品
*
* 算法核心思路:
* 这是一个贪心策略的体现 - 先尽可能使用课堂券消耗较少的兑换方式
* 1. 首先假设全部使用第一种兑换方式a张课堂券 + b张作业券
* 2. 如果作业券数量超过小A持有的数量则需要将部分兑换改为第二种方式
* 3. 每将一份兑换从第一种方式改为第二种方式:
* - 课堂券需求变化从a变为b增加了b-a张
* - 作业券需求变化从b变为a减少了b-a张
* 4. 最后检查调整后的券数量是否都不超过小A持有的数量
*/
int check(int v) {
// 使用LL类型防止整数溢出
LL x, y, t;
// 步骤1先假设全部使用第一种方式课堂券少、作业券多
x = 1ll * v * a; // 计算所需的课堂券总数
y = 1ll * v * b; // 计算所需的作业券总数
// 步骤2如果作业券不足需要调整兑换方式
if (y > m) {
// 计算需要调整的兑换份数t
// 公式原理:
// - (y - m)是超出的作业券数量
// - 每调整一份兑换方式,可以减少(b-a)张作业券需求
// - 使用上取整公式确保足够的调整量,避免余数导致仍不足
t = (y - m + (b - a) - 1) / (b - a);
// 步骤3调整后的券数量计算
y -= t * (b - a); // 作业券减少量:每份调整减少(b-a)张
x += t * (b - a); // 课堂券增加量:每份调整增加(b-a)张
}
// 步骤4验证调整后的总需求是否在小A持有的券数量范围内
return x <= n && y <= m;
}
/**
* 主函数
*
* 算法流程:
* 1. 输入小A持有的课堂券和作业券数量
* 2. 输入兑换方式参数两种兑换方式a课堂+b作业 或 b课堂+a作业
* 3. 预处理确保n ≤ ma ≤ b简化后续计算逻辑
* 4. 特殊情况处理:如果两种兑换方式相同(a == b),直接计算结果
* 5. 使用二分查找确定最大可兑换的奖品数量
*/
int main() {
// 输入小A持有的课堂优秀券和作业优秀券数量
cin >> n >> m;
// 输入第一种兑换方式所需的两种券数量
// 注意根据题目设定第二种兑换方式是b张课堂券和a张作业券
cin >> a >> b;
// 预处理1确保n <= m
// 目的:
// 1. 保证我们总是用较小的那个券数量作为二分查找的右边界上限
// 2. 由于后续我们会优先使用课堂券较少的兑换方式n(课堂券)应该不大于m(作业券)
if (n > m)
swap(n, m);
// 预处理2确保a <= b
// 目的:
// 1. 确保第一种兑换方式是"课堂券少、作业券多"的组合
// 2. 这是贪心策略的基础,优先使用课堂券消耗较少的方式
if (a > b)
swap(a, b);
// 特殊情况处理:如果两种兑换方式相同
// 说明无论选择哪种方式,兑换一份奖品所需的课堂券和作业券数量相同
if (a == b) {
// 直接返回较小的券数量除以每份所需的券数量
// 因为n <= m且a == b所以n/a就是最大可兑换数量
printf("%d\n", n / a);
return 0;
}
// 二分查找初始化
// 左边界l设为0最少兑换0份奖品
// 右边界r设为n由于n <= m且a <= b最多不能超过n/a份而n是较小值
l = 0;
r = n;
// 二分查找过程
// 使用"左闭右闭"区间的二分查找模板,这里采用了向上取整的中点计算方式
while (l < r) {
// 计算中点mid使用(l + r + 1) >> 1的原因
// 1. 这是一种避免死循环的技巧
// 2. 在查找上界时,如果使用普通中点(l + r)/2可能会导致l无法到达r
// 3. 右移一位(>> 1)相当于除以2取整但比除法运算更高效
int mid = (l + r + 1) >> 1;
// 检查是否可以兑换mid份奖品
if (check(mid))
// 如果可以兑换mid份说明我们可以尝试兑换更多将左边界移到mid
l = mid;
else
// 如果不能兑换mid份说明需要减少兑换数量将右边界移到mid-1
r = mid - 1;
}
// 输出结果此时l和r相等都是最大可兑换的奖品数量
printf("%d\n", r);
return 0;
}