二分查找初学

252 阅读3分钟

以一个二分查找的经典例题作为说明 洛谷P2678传送门 这一题如果你使用暴力枚举的话, 铁定会超时, 故使用二分查找的方法.

顾名思义,它用二分的方法枚举答案,并且枚举时判断这个答案是否可行。但是,二分并不是在所有情况下都是可用的,使用二分需要满足两个条件。一个是有界,一个是单调。

二分答案应该是在一个单调闭区间上进行的。也就是说,二分答案最后得到的答案应该是一个确定值,而不是像搜索那样会出现多解。二分一般用来解决最优解问题。

由于这一题是要求得最短的跳跃距离中的最大的, 于是我们可以对跳跃距离进行二分, 对于mid的跳跃距离, 我们就对它执行judge函数, judge函数的功能是检测这个解是不是合法。拿这个题来说,我们去判断如果以这个距离为最短跳跃距离需要移走多少块石头,先不必考虑限制移走多少块,等全部拿完再把拿走的数量和限制进行比对,如果超出限制,那么这就是一个非法解,反之就是一个合法解.

可以去模拟这个跳石头的过程。开始你在i(i=0)位置,我在跳下一步的时候去判断我这个当前跳跃的距离,如果这个跳跃距离比二分出来的mid小,那这就是一个不合法的石头,应该移走。为什么?我们二分的是最短跳跃距离,已经是最短了,如果跳跃距离比最短更短岂不是显然不合法,是这样的吧。移走之后要怎么做?先把计数器加上1,再考虑向前跳啊。去看移走之后的下一块石头,再次判断跳过去的距离,如果这次的跳跃距离比最短的长,那么这样跳是完全可以的,我们就跳过去,继续判断,如果跳过去的距离不合法就再拿走,这样不断进行这个操作,直到i = n+1,为啥是n+1?河中间有n块石头,显然终点在n+1处。(这里千万要注意不要把n认为是终点,实际上从n还要跳一步才能到终点)。

模拟完这个过程,我们查看计数器的值,这个值代表的含义是我们以mid作为答案需要移走的石头数量,然后判断这个数量 是不是超了就行。如果超了就返回false,不超就返回true。

#include <bits/stdc++.h>
#define maxn 500010
using namespace std;

int l, r, n, m, ans, mid, d;
int a[maxn];

bool judge(int x) {
    int tot = 0;
    int i = 0;
    int now = 0;

    while(i < n + 1) {
        i++;
        if (a[i] - a[now] < x) {
            //那么就将这块石头拿走
            tot++;
        } else {
            now = i;
        }
    }
    if (tot > m) {
        return false;
    } else {
        return true;
    }
}

int main() {
    cin >> d >> n >> m;

    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }

    a[n + 1] = d;

    l = 1, r = d;
    while (l <= r) {
        mid = (l + r) / 2;
        if (judge(mid)) {
            ans = mid;
            l = mid + 1;
        }
        else r = mid - 1;
    }
    cout << ans << endl;
    return 0;
}