「二分」愤怒的牛

本题为1月12日22寒假集训每日一题题解

题目来源:USACO 2005 Feb. Gold

题面

题目描述

农夫约翰建造了一座有n间牛舍的小屋,牛舍排在一条直线上,第i间牛舍在 $ x_i $ 的位置,但是约翰的m头牛对小屋很不满意,因此经常互相攻击。约翰为了防止牛之间互相伤害,因此决定把每头牛都放在离其它牛尽可能远的牛舍。也就是要最大化最近的两头牛之间的距离。

牛们并不喜欢这种布局,而且几头牛放在一个隔间里,它们就要发生争斗。为了不让牛互相伤害。John决定自己给牛分配隔间,使任意两头牛之间的最小距离尽可能的大,那么,这个最大的最小距离是多少呢?

输入

第一行用空格分隔的两个整数n和m;

第二行为n个用空格隔开的整数,表示位置xi。

输出

输出仅一个整数,表示最大的最小距离值。

样例输入

5 3
1 2 8 4 9

样例输出

3

提示

样例解释:

把牛放在 11, 44 ,88 这样最小距离是 33 。

数据范围:

$ 2 \leq n \leq 10^5 $ , $ 0 \leq x_i \leq 10^9 $ , $ 2\leq m \leq n $。


思路分析

此题题面有点绕,总而言之就是让你最大化最近两头牛之间的距离,显然,这是一个最大化最小值的问题,可以通过二分法来解决.

此处要使用二分法,我们的一个核心问题是,对于给出的一个最近两头牛之间的距离,我们要怎么判断它是否成立.

注意到这里牛舍之间的距离只能取得某几个值,在整数上并不一定是连续取的,且我们最终求的是最大值,所以我们并不需要判断能不能严格取得这个值,而是只需要判断能否使得最近的两头牛之间的距离不小于这个值,即能否使得任意两头牛之间的距离均不小于这个值.

所以我们只需要将牛舍排序,从第一个牛舍开始安排牛,然后找其后不小于给定距离的最近的牛舍,在其中安排下一头牛,接着继续如法炮制找下一个牛舍.如果在安排完所有牛之前已经无牛舍可用,则可知这个距离不成立,即这个距离太大,我们将二分区间大的一半砍掉;反之则是成立,我们将二分区间小的一半砍掉,去寻找更大的距离.

参考代码

我个人习惯二分的区间是两端都闭的,而不是常规的左闭右开,所以二分部分缩小区间的方式以及最后的输出可能会与普遍写法不同.

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3, "Ofast", "inline")

#include <iostream>
#include <algorithm>

using namespace std;

int main()
{
    ios::sync_with_stdio(false);

    int n, m;
    cin >> n >> m;

    int *x = new int[n]; // 牛舍
    for (int i = 0; i < n; i++)
    {
        cin >> x[i];
    }

    sort(x, x + n); // 使牛舍按坐标排序

    // 二分
    int l = 0, r = 1000000000, mid;
    while (r >= l)
    {
        int mid = l + ((r - l) >> 1);

        // 判断当前的距离能否被满足
        bool flag = true;
        int prev = 0;                // 从第一个牛舍开始
        for (int i = 1; i < m; i++) // 第一个牛舍安置了第一头牛,所以只需要继续安置m-1头牛即可
        {
            // 找其后不小于给定距离的最近的牛舍
            int crt = prev + 1;
            while (crt < n && x[crt] - x[prev] < mid)
            {
                crt++;
            }

            // 在安排完所有牛之前已经无牛舍可用,则可知这个距离不成立
            if (crt >= n)
            {
                flag = false;
                break;
            }

            prev = crt; // 更新前一个牛舍的位置
        }

        if (flag) // 当前距离能被满足,尝试寻找更大
        {
            l = mid + 1;
        }
        else // 当前距离太大,不能被满足,将大的一半区间砍掉
        {
            r = mid - 1;
        }
    }

    cout << r << "\n"; // 区间不存在时,原本的右指针(此时在左侧)指向的即是最大的可行距离

    delete x;
    x = nullptr;
    return 0;
}

"正是我们每天反复做的事情,最终造就了我们,优秀不是一种行为,而是一种习惯" ---亚里士多德

posted @ 2023-01-12 16:45  星双子  阅读(39)  评论(0编辑  收藏  举报