二分总结

本文最后更新于 2022年1月8日 下午

以前学习二分的时候一直没有搞清楚什么时候,什么时候,更新的时候是还是,但是一直迷迷糊糊的也能写对题,后面也没有多管。这个寒假重新学一遍基础算法,才明白为什么会有这两种情况。

整数二分的两种情况

第一种
这种情况代码如下

while (l < r)
    {
        int mid = l + r + 1 >> 1;
        if (check(mid))
            l = mid;
        else
            r = mid - 1;
    }

为什么需要补上一个呢?

因为整数的除法是向下取整,我们需要在运算时+1时它变成向上取整,否则的话会因为边界问题导致while无限循环。
举个例子,在区间只有两个数的时候,比如,这时候如果check成功,那么会执行,然后问题就来了,本来就都等于3,然后就会无限循环下去,的值永远不会更新。所以在的更新方式下,我们需要将运算时成为向上取整才不会死循环。

第二种
这种情况代码如下

while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid))
            l = mid + 1;
        else
            r = mid;
    }

为什么在这里不需要补上一个呢?

因为边界问题不会影响到值的更新,还是用上面那个例子,
此时如果check成功,那么,更新成功。如果check失败,则,同样更新成功。由此可以看出,在的更新方式下,不需要对进行任何操作。

以上是整数二分的两种固定写法。那么什么时候用哪种方法呢,这就要根据具体的题目进行分析了。


例题—— 分巧克力

题目描述

儿童节那天有 K 位小朋友到小明家做客。
小明拿出了珍藏的巧克力招待小朋友们。
小明一共有 N 块巧克力,其中第 i 块是 Hi×Wi 的方格组成的长方形。
为了公平起见,小明需要从这 N 块巧克力中切出 K 块巧克力分给小朋友们。
切出的巧克力需要满足:
形状是正方形,边长是整数
大小相同
例如一块 6×5 的巧克力可以切出 6 块 2×2 的巧克力或者 2 块 3×3 的巧克力。
当然小朋友们都希望得到的巧克力尽可能大,你能帮小明计算出最大的边长是多少么?

输入格式

第一行包含两个整数 N 和 K。
以下 N 行每行包含两个整数
输入保证每位小朋友至少能获得一块 的巧克力。

输出格式

输出切出的正方形巧克力最大可能的边长。

输入输出样例

输入#1
2 10
6 5
5 6

输出#1
2

AC代码

#include <algorithm>
#include <cmath>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <stack>
typedef long long ll;
#define IOS ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
#define max(a, b) (a > b ? a : b)
#define min(a, b) (a < b ? a : b)
#define endl '\n'
using namespace std;
int n, k;
const int N = 1e5 + 10;
int h[N], w[N];

bool check(int x)
{
    int cnt = 0;
    for (int i = 0; i < n; i++)
    {
        cnt += (h[i] / x) * (w[i] / x);
        if (cnt >= k)
            return true;
    }
    return false;
}

int main()
{
    IOS;
    cin >> n >> k;
    for (int i = 0; i < n; i++)
        cin >> h[i] >> w[i];
    int l = 0, r = N;
    while (l < r)
    {
        int mid = l + r + 1 >> 1;
        if (check(mid))
            l = mid;
        else
            r = mid - 1;
    }
    cout << l << endl;
    return 0;
}

题目很简单是二分的板子题,主要是说为什么要用第一种方法呢。
为什么二分里面不能写成

int mid = l + r >> 1;
if (check(mid))
     l = mid + 1;        //这样写跑样例答案是3
else
     r = mid;

推导一遍发现,如果成立后,说明当前的值也是在答案范围中,所以答案只能在之中取。理所当然无法用上面那种写法,如果用上面那种方法,会直接更新成,会越过这个值,所以在一些情况就会出现错误,比如题面的样例就会成为,越过了这个可能的答案值,恰好就是最大的一个答案,导致答案错误。


总结

这次弄明白了二分的写法属实是不易,留文一篇防止日后遗忘
以后写二分的时候记得注意一下区间情况


二分总结
https://exusiai.top/article/a1bbfb3e34b5.html
作者
HauKuen
发布于
2022年1月7日
许可协议