P13013 GESP5级202506 编程T1--奖品兑换
P13013 [GESP202506 五级] 奖品兑换
题目背景
为了保证只有时间复杂度正确的代码能够通过本题,时限下降为 400 毫秒。
题目描述
班主任给上课专心听讲、认真完成作业的同学们分别发放了若干张课堂优秀券和作业优秀券。同学们可以使用这两种券找班主任兑换奖品。具体来说,可以使用 aaa 张课堂优秀券和 bbb 张作业优秀券兑换一份奖品,或者使用 bbb 张课堂优秀券和 aaa 张作业优秀券兑换一份奖品。
现在小 A 有 nnn 张课堂优秀券和 mmm 张作业优秀券,他最多能兑换多少份奖品呢?
输入格式
第一行,两个正整数 n,mn,mn,m,分别表示小 A 持有的课堂优秀券和作业优秀券的数量。
第二行,两个正整数 a,ba,ba,b,表示兑换一份奖品所需的两种券的数量。
输出格式
输出共一行,一个整数,表示最多能兑换的奖品份数。
输入输出样例 #1
输入 #1
8 8
2 1
输出 #1
5
输入输出样例 #2
输入 #2
314159 2653589
27 1828
输出 #2
1599
说明/提示
对于 60%60\%60% 的测试点,保证 1≤a,b≤1001 \le a,b \le 1001≤a,b≤100,1≤n,m≤5001 \le n,m \le 5001≤n,m≤500。
对于所有测试点,保证 1≤a,b≤1041 \le a,b \le 10^41≤a,b≤104,1≤n,m≤1091 \le n,m \le 10^91≤n,m≤109。
Bilibili视频
先康康我的bilibili视频
(进度条不对劲)家人们出事啦!我用O(2^n)的递归方式,居然AC了n在10000以内的题目!
10分钟,很长的。
这个视频针对这道题给出了三种解法:递归、贪心、二分。
如果考试问你学到什么道理,直接抄上!

题目大意
给定四个正整数nnn,mmm,aaa,bbb,分别表示手里的课堂优秀券、手里的作业优秀券、一个奖品需要的课堂优秀券、一个奖品同时需要的作业优秀券(aaa,bbb可以随时交换,视频忘说了)
递归解法
假设递归函数为dfs
,那么应该传入这些参数:i j cnt
,表示剩余课堂优秀券、剩余作业优秀券、已获得奖品数量。
对于dfs(i,j,cnt)
可以推出dfs(i-a,j-b,cnt+1)
和dfs(i-b,j-a,cnt+1)
,前提是够换奖品。
先别管超时,我们先写代码。
#include<bits/stdc++.h>
using namespace std;
int a,b,mx;
void dfs(int i,int j,int cnt)
{mx=max(mx,cnt);if(i>=a&&j>=b)dfs(i-a,j-b,cnt+1);if(i>=b&&j>=a)dfs(i-b,j-a,cnt+1);
}
int main()
{int n,m;cin>>n>>m>>a>>b;dfs(n,m,0);cout<<mx;return 0;
}
一号样例过
遇到二号就噶了
提交逝世
我怎么感觉二号样例有助于我们背圆周率呢
这里有12个AC,4个TLE,4个MLE,其中tle是超时,mle是超内存(超递归栈)
递归复杂度
每个递归函数引导两个子函数,差不多O(2n)O(2^n)O(2n)吧?
空间应该就是O(n)O(n)O(n)了,细致的说,O(na+mb)O(\frac{n}{a}+\frac{m}{b})O(an+bm)(并不准)
贪心
我们尽可能地让n,mn,mn,m中的较大者去减a,ba,ba,b中的较大者,较小者同理,这样分配对n,mn,mn,m公平,可以多分点。
于是代码就"bang"出来了
#include<bits/stdc++.h>
using namespace std;
int main()
{int n,m,a,b,cnt=0;cin>>n>>m>>a>>b;while(n>=a&&m>=b||n>=b&&m>=a) //c++中,先算&&,再算||{if(n>=m) //n大,优先选n-max a,b{n-=max(a,b);m-=min(a,b);}else //m大,优先选m-max a,b{n-=min(a,b);m-=max(a,b);}cnt++;}cout<<cnt;return 0;
}
样例1、2全都能通过
然而提交时的你又双叒叕(you4 shuang1 ruo4 zhuo2)傻眼了
哇!第17个点正好超时1ms
就在你想放弃时,突然点到了展开算法标签。
二分!?我想试试。
有谁注意到了
二分AC版
我们假设最多兑换的奖品份数为xxx,思考🤔如何判断xxx是不是合法的奖品份数(小于等于标准答案)。
首先调整a<ba<ba<b以便后期操作。
当n≥axn\ge axn≥ax并且m≥axm\ge axm≥ax时,必然兑换够用!其中第二种利用aaa比bbb小,尽量让后面的剩余资源空间大。
那么如果是两种方案混合来呢?
我们来看一句话:
“今有鸡兔同笼,上有三十四头,下有九十四足,问鸡兔各几何?”
我们一般用假设法来解决鸡兔同笼问题,设全都是什么,然后把一部分变成另一种,最后求出数量。
那么这道题也可以用假设法。
现在假设全都是a,ba,ba,b类(aaa课堂bbb作业),要想改成b,ab,ab,a类,就要多搞来b−ab-ab−a个课堂优秀券。而n−axn-axn−ax是剩下的课堂优秀券,因此⌊n−axb−a⌋\lfloor\frac{n-ax}{b-a}\rfloor⌊b−an−ax⌋就是b,ab,ab,a类的数量,同理。
假设全都是b,ab,ab,a类,要想改成a,ba,ba,b类,就要多搞来b−ab-ab−a个作业优秀券。而m−axm-axm−ax是剩下的作业优秀券(因为a,ba,ba,b可以互换,aaa更小剩余的更大),因此⌊m−axb−a⌋\lfloor\frac{m-ax}{b-a}\rfloor⌊b−am−ax⌋就是a,ba,ba,b类的数量。
如果他们加起来小于xxx,就说明xxx太大了,因此可以把他们存进check
函数,对于后面的二分答案有帮助。
bool check(int num)
{if(n<a*num || m<a*num)return false;int ab=(m-a*num)/(b-a),ba=(n-a*num)/(b-a);return ab+ba>=num;
}
因为二分经常写错,所以提一点
1 当check(mid)
返回true
,l=mid
,因为合法, l
不能加111
2 当check(mid)
返回false
,r=mid-1
,因为不合法,r
必须减111
3 mid=(l+r+1)/2
,因为l=3 r=4
时,(l+r)/2
且check(3)=true
时,mid
会一直等于333,l
永远不变,也就超时了,(l+r+1)/2
可以避免。
4 l<r
是循环进入条件,如果有等于那么l
和r
会永久重合,TLE了。
知道这一点,没问题了,开始写Code吧!
AC Code
#include<bits/stdc++.h>
using namespace std;
int n,m,a,b;
bool check(int num)
{if(n<a*num || m<a*num)return false; //基础资源分配int ab=(m-a*num)/(b-a),ba=(n-a*num)/(b-a); //a,b类b,a类转换return ab+ba>=num;
}
int main()
{cin>>n>>m>>a>>b;if(a>b)swap(a,b); //调整a和b大小if(a==b){cout<<min(n,m)/a;return 0;} //直接计算long long l=0,r=2e9;while(l<r){int mid=(l+r+1)/2;if(check(mid))l=mid; //合法else r=mid-1; //不合法}cout<<l;return 0;
}
目前这个代码是能通过样例的,l r
必须开long long
,mid
可以不用。
…
孩子们!mid
要开long long
,想起那个WA
时钢管掉落的声音了吗!
因为check
里面对mid
和其他变量进行了乘法,必须开long long
!!!
#include<bits/stdc++.h>
using namespace std;
int n,m,a,b;
bool check(int num)
{if(n<a*num || m<a*num)return false; //基础资源分配int ab=(m-a*num)/(b-a),ba=(n-a*num)/(b-a); //a,b类b,a类转换return ab+ba>=num;
}
int main()
{cin>>n>>m>>a>>b;if(a>b)swap(a,b); //调整a和b大小if(a==b){cout<<min(n,m)/a;return 0;} //直接计算long long l=0,r=2e9;while(l<r){long long mid=(l+r+1)/2;if(check(mid))l=mid; //合法else r=mid-1; //不合法}cout<<l;return 0;
}
样例不用说了肯定对了
(一种植物)
孩子们!check
里面的num
也要开long long
!!!
(一个二分代码改了三次)
#include<bits/stdc++.h>
using namespace std;
int n,m,a,b;
bool check(long long num)
{if(n<a*num || m<a*num)return false; //基础资源分配int ab=(m-a*num)/(b-a),ba=(n-a*num)/(b-a); //a,b类b,a类转换return ab+ba>=num;
}
int main()
{cin>>n>>m>>a>>b;if(a>b)swap(a,b); //调整a和b大小if(a==b){cout<<min(n,m)/a;return 0;} //直接计算long long l=0,r=2e9;while(l<r){long long mid=(l+r+1)/2;if(check(mid))l=mid; //合法else r=mid-1; //不合法}cout<<l;return 0;
}
啊!终于AC了
总结
这道题其实难在二分公式的数学推导,其他没啥大问题,注意long long
就行。
GodBye