'commit'
This commit is contained in:
125
GESP/Level5/GESP202506/T1.cpp
Normal file
125
GESP/Level5/GESP202506/T1.cpp
Normal 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 ≤ 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;
|
||||
}
|
||||
Reference in New Issue
Block a user