题目描述:
小蓝有一个神奇的炉子用于将普通金属 O 冶炼成为一种特殊金属 X。
这个炉子有一个称作转换率的属性 V,V是一个正整数,这意味着消耗 V个普通金属 O 恰好可以冶炼出一个特殊金属 X,当普通金属 O 的数目不足 V 时,无法继续冶炼。
现在给出了 N 条冶炼记录,每条记录中包含两个整数 A和 B,这表示本次投入了 A 个普通金属 O,最终冶炼出了 B 个特殊金属 X。
每条记录都是独立的,这意味着上一次没消耗完的普通金属 O 不会累加到下一次的冶炼当中。
根据这 N 条冶炼记录,请你推测出转换率 V 的最小值和最大值分别可能是多少,题目保证评测数据不存在无解的情况。
输入格式:
第一行一个整数 N,表示冶炼记录的数目。
接下来输入 N 行,每行两个整数 A、B含义如题目所述。
输出格式:
输出两个整数,分别表示 V 可能的最小值和最大值,中间用空格分开。
数据范围:
对于 30% 的评测用例,1≤N≤1e2。
对于 60% 的评测用例,1≤N≤1e3。
对于 100% 的评测用例,1≤N≤1e4,1≤B≤A≤1e9。
输入样例:
3
75 3
53 2
59 2
输出样例:
20 25
样例解释:
当 V=20时,有:⌊75/20⌋=3 , ⌊53/20⌋=2,⌊59/20⌋=2,可以看到符合所有冶炼记录。
当 V=25 时,有:⌊75/25⌋=3,⌊53/25⌋=2,⌊59/25⌋=2,可以看到符合所有冶炼记录。
且再也找不到比 20 更小或者比 25 更大的符合条件的 V 值了。
分析步骤:
第一:拿到这道题目读完之后,我们其实可以感觉到这是一个数学问题,所以它存在一个公式如果把它推到出来了就直接套公式就可以解出来问题;但是事实上,求公式非常困难,所以我们也可以换一种方法去做这道题目。我们注意观察,其实金属转化率V其实是单调的。因为当所需要的普通金属越多时,金属转化率越低,这刚好符合 y = 1/x 这个函数的图像,只不过他不连续而是分段的,一段一段有最小值和最大值,在这个区间中,转化得到的特殊金属x是不变个数的。而且中我们从图中可以看出:当V小于Vmin时B都比答案要大,当V大于Vmax时B都要比答案要小,所以我们找到了他的边界条件,注意:我们分析出了单调,边界情况,所以应该想到运用二分法求答案。
第二:书写主函数构建大致框架,首先把值全都输入进去;接着开始二分:我们的左边界我们定义l为1因为题目不会出现无解的情况并且当l为1时可能可以得到最多的特殊金属X;我们的右边界定义r为1e9因为题目不会出现无解的情况并且当r为1e9时可能可以得到最少的特殊金属X。所以这是两种极端情况,二分的边界就得是极端情况,得把所有的可能都包括进来。
int main()
{
cin>>n;
for(int i = 0 ; i < n ; i++){
cin>>q[i].x>>q[i].y;
}
int l = 1 , r = 1e9;
while(l < r){
int mid =(l + r) / 2;
if(check1(mid)){
r = mid;
}else{
l = mid + 1;
}
}
cout<<l<<" ";
l = 1 , r = 1e9;
while(l < r){
int mid =(l + r + 1) / 2;
if(check2(mid)){
l = mid;
}else{
r = mid - 1;
}
}
cout<<l<<" ";
return 0;
}
进入while循环找Vmin.首先把mid的值算出来,并且判断一下mid的值是否可行,如果可以(计算得出的B 比 答案更小或等于)我们就把 mid 的值 赋值给 r 因为mid有可能是答案而且还有可能有比mid更小的答案所以应该更新区间【l , r(mid)】; 如果 mid 的 值不可行(计算得出的B 比 答案更大) 则把 mid+1 的值赋给 l 因为mid已经确定了不可以,v必须是整数,所以有可能下一个数是正确答案,所以更新区间【l(mid + 1) , r】;
int l = 1 , r = 1e9;
while(l < r){
int mid =(l + r) / 2;
if(check1(mid)){
r = mid;
}else{
l = mid + 1;
}
}
cout<<l<<" ";
进入while循环找Vmax.首先把mid的值算出来,并且判断一下mid的值是否可行,如果可以(计算得出的B 比 答案更大或等于)我们就把 mid 的值 赋值给 l 因为mid有可能是答案而且还有可能有比mid更大的答案所以应该更新区间【l(mid) , r】; 如果 mid 的 值不可行(计算得出的B 比 答案更小) 则把 mid-1 的值赋给 r 因为mid已经确定了不可以,v必须是整数,所以有可能下一个数是正确答案,所以更新区间【l , r(mid - 1)】;
l = 1 , r = 1e9;
while(l < r){
int mid =(l + r + 1) / 2;
if(check2(mid)){
l = mid;
}else{
r = mid - 1;
}
}
cout<<l<<" ";
注意!!:大家可以对照着图片看看,什么情况是可以,什么情况是不可以。另外这两次的mid求值公式不一样,大家记住就行如果mid赋值给l则mid求值公式中要+1,反之则不要。
第三:书写check1函数(求Vmin),如果q[i].x / mid > q[i].y 则返回false 。等待for循环结束,则返回true。对应的对错中有对应的修改方案。注意我们现在是求Vmin所以应该是>号
bool check1(int mid){
for(int i = 0 ; i < n ; i ++){
if(q[i].x / mid > q[i].y) return false;
}
return true;
}
第四:书写check2函数(求Vmax)如果q[i].x / mid < q[i].y 则返回false 。等待for循环结束,则返回true。对应的对错中有对应的修改方案。注意我们现在是求Vmin所以应该是<号
bool check2(int mid){
for(int i = 0 ; i < n ; i ++){
if(q[i].x / mid < q[i].y) return false;
}
return true;
}
大家还是可以对着图片来观察这道题。更容易理解。
代码:
#include <iostream>
#include <cstring>
#include <algorithm>
#define x first
#define y second
using namespace std;
typedef long long ll;
typedef pair<ll, ll> PII;
const int N = 1e5+10;
PII q[N];
int n;
bool check1(int mid){
for(int i = 0 ; i < n ; i ++){
if(q[i].x / mid > q[i].y) return false;
}
return true;
}
bool check2(int mid){
for(int i = 0 ; i < n ; i ++){
if(q[i].x / mid < q[i].y) return false;
}
return true;
}
int main()
{
cin>>n;
for(int i = 0 ; i < n ; i++){
cin>>q[i].x>>q[i].y;
}
int l = 1 , r = 1e9;
while(l < r){
int mid =(l + r) / 2;
if(check1(mid)){
r = mid;
}else{
l = mid + 1;
}
}
cout<<l<<" ";
l = 1 , r = 1e9;
while(l < r){
int mid =(l + r + 1) / 2;
if(check2(mid)){
l = mid;
}else{
r = mid - 1;
}
}
cout<<l<<" ";
return 0;
}