Files
python/GESP/Level5/GESP202506/T1.cpp
HuangHai 6cd42b8ec2 'commit'
2025-09-21 15:19:06 +08:00

126 lines
4.8 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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 = ceil((y - m) / (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;
}