diff --git a/GESP/Level5/GESP202506/T1.cpp b/GESP/Level5/GESP202506/T1.cpp new file mode 100644 index 0000000..3b83883 --- /dev/null +++ b/GESP/Level5/GESP202506/T1.cpp @@ -0,0 +1,125 @@ +#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; +}