AcWing 4956. 冶炼金属 ---利用二分查找 容易理解的方法
题目
小蓝有一个神奇的炉子用于将普通金属 \(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 ≤ 10^2\)。
对于 \(60\%\) 的评测用例,\(1 ≤ N ≤ 10^3\)。
对于 \(100\%\) 的评测用例,\(1 ≤ N ≤ 10^4\),\(1 ≤ B ≤ A ≤ 10^9\)。
输入样例:
3
75 3
53 2
59 2
输出样例:
20 25
样例解释
当 \(V = 20\) 时,有:\(⌊\frac{75}{20} ⌋ = 3\),\(⌊\frac{53}{20} ⌋ = 2\),\(⌊\frac{59}{20} ⌋ = 2\),可以看到符合所有冶炼记录。
当 \(V = 25\) 时,有:\(⌊\frac{75}{25} ⌋ = 3\),\(⌊\frac{53}{25} ⌋ = 2\),\(⌊\frac{59}{25} ⌋ = 2\),可以看到符合所有冶炼记录。
且再也找不到比 \(20\) 更小或者比 \(25\) 更大的符合条件的 \(V\) 值了。
题解
好理解的二分方法
如果二分基本知识不牢的话,推荐看这篇博客 https://blog.csdn.net/raelum/article/details/128687109
二分实质是将准备求的边界 划分为两个对立的区间
由于转换率 V的取值一定是小于普通金属的数目A的,由题\(1≤B≤A≤10^9\) 所以我们将V的取值范围也设在\(1≤V≤10^9\) 这就是我们所要二分的边界
这里也有个要注意的点用SL的check(mid)举例
由于有n个A和B,我们要在每一个a[i].first / mid <= a[i].second 都判断成功时, 才能输出true,这样写起来很麻烦
所以我们将判断条件反着写,一旦a[i].first / mid <= a[i].second不成立,就判断为false
也就是只要a[i].first / mid > a[i].second 就为false,当循环内都不满足 才输出true
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 10010;
typedef pair<int, int> PII;
int n;
PII a[N];
bool check_1(int mid) //SL的判断条件
{
for (int i = 0; i < n; i ++ )
if(a[i].first / mid > a[i].second) return false;
return true;
}
bool check_2(int mid) //RL的判断条件
{
for (int i = 0; i < n; i ++ )
if(a[i].first / mid < a[i].second) return false;
return true;
}
int SL(int l, int r) //求左边界
{
while (l < r)
{
int mid = l + r >> 1;
if(check_1(mid)) r = mid;
else l = mid + 1;
}
return l;
}
int RL(int l, int r) //求右边界
{
while (l < r)
{
int mid = l + r + 1 >> 1;
if(check_2(mid)) l = mid;
else r = mid - 1;
}
return l;
}
int main()
{
scanf("%d", &n);
for (int i = 0; i < n; i ++ ) scanf("%d%d", &a[i].first, &a[i].second);
int l = 1, r = 1e9; //因为这里写成int l = 0, r = 1e9; debug了两个小时!
printf("%d ", SL(l, r));
r = 1e9; //求出左边界后,我们求右边界时就可以从求出的左边界到1e9来查找右边界
printf("%d\n", RL(l, r));
return 0;
}
这里最开始写成int l = 0, r = 1e9; debug了两个小时!
原因:因为l~r这个区间是用来求除数V的,当l可以取0时,我们就有概率赋值到 l + r = mid=0这个值
在check函数内,a[i].first / mid,mid = 0, 除数就是0,当除数为0会形成Float Point Exception错误
然后测试的最后一组数据非常地恶心,左右边界都是1 1,所以当我们求左边界时,初始值mid = l + r >> 1,会一直满足check_1(mid),r = mid 一直进行,l + r >> 1 一直进行
最终mid = 1时,check_1(mid)成立, r = mid = 1,下一次循环mid = l + r >> 2 就等于0了(向下取整)
于是在check_1(mid)就会发生Float Point Exception错误 所以一定要注意边界问题!
实际上是审题不清