#include 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 ≤ m,a ≤ 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; }