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\) 这就是我们所要二分的边界
9f1a191d93cc7576bdba328a53d6eb6.jpg
这里也有个要注意的点用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
43cf94a2616b649dffd86a5762e87f4.png

#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;
}

1aa6db8b66d2143e79aee1c4f4fcc5a.png
这里最开始写成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错误 所以一定要注意边界问题!
实际上是审题不清

posted @ 2024-04-16 17:35  MsEEi  阅读(4)  评论(0编辑  收藏  举报