侵略性奶牛(二分算法的总结)

题目
Farmer John has built a new long barn, with N (2 <= N <= 100,000) stalls. The stalls are located along a straight line at positions x1,…,xN (0 <= xi <= 1,000,000,000). His C (2 <= C <= N) cows don’t like this barn layout and become aggressive towards each other once put into a stall. To prevent the cows from hurting each other, FJ want to assign the cows to the stalls, such that the minimum distance between any two of them is as large as possible. What is the largest minimum distance?

农夫 John 建造了一座很长的畜栏,它包括N (2 <= N <= 100,000)个隔间,这些小隔间依次编号为x1,…,xN (0 <= xi <= 1,000,000,000). 但是,John的C (2 <= C <= N)头牛们并不喜欢这种布局,而且几头牛放在一个隔间里,他们就要发生争斗。为了不让牛互相伤害。John决定自己给牛分配隔间,使任意两头牛之间的最小距离尽可能的大,那么,这个最大的最小距离是什么呢

输入:
第一行:空格分隔的两个整数N和C
第二行—第N+1行:i+1行指出了xi的位置

输出:
第一行:一个整数,最大的最小值

**样例输入**
5 3
1
2
8
4
9
**样例输出**
3

1. 将贪心问题转换为判定类问题

题目希望的求解最小距离最大,此类贪心问题往往可以转换为判定某一个条件(此处是距离)是否可以找到一个对应的解,在此我们只要实现一个函数 bool judge(int, int, int) 进行判定,即可解出某一情况是否有解也就是某一个距离能否满足题目所需要的每两只奶牛之间距离均不小于此距离。从而方便了我们在搜索区间上(此处是距离的区间:即最小距离0到最大距离两段牛棚的距离)探索最优解。

2. 二分法区间移动过程

探索可以使用遍历,但是对于有序序列来说,二分是一个更好的策略。二分可以通过更低的时间复杂度 (logN) 求解问题,下面讲解二分的思路。

思路:

首先,二分需要的条件就是有序,而这样的最值问题希望的答案就简单的绑定在我们的求解区间上,是区间的极值,和二分方法的思路是十分吻合的,因而使用二分的思路是合理的。

二分的思想:

应当更加深刻的理解二分,二分是一种对于区间性质的划分。这里拿最简单的二分查找举例,二分查找的关键点不应当在middle对应的数是否是我希望查找的数,而应当是某一段区间应当被划定为什么样的状态。left和right指针需要将线性区间划分为三个部分:

  1. left左部区间内一定不存在要寻找的数。
  2. right右部区间内一定不存在要寻找的数。
  3. left本身到right本身区间内可能存在要寻找的数。

这样就应该更好理解其中的一些步骤中的判断条件,比如:

  1. middle比较不等之后为什么left = middle + 1或right = middle - 1而不是等于middle。因为middle此时也已经被确定为在不存在要寻找数的区间内了。
  2. 为什么循环终止的条件是left > right而不是>=。因为大于等于(>=)时还是能留下一段可能区间,只有在大于(>)时不存在要寻找的数的区间才会完全覆盖整个区间。

二分的指针移动:

应用以上的思想就可以很好的解释本题的代码,区间被划分为三部分

  1. left左部区间内的距离一定是可以找到一种放置方案。
  2. right右部区间内的距离一定无法找到可行的放置方案。
  3. left本身到right本身区间内可能存在可行的放置方案。

这可以很好的解释为什么此处的二分不能够在中途跳出,必须完全实现,left<right时才能跳出,因为这里不是像二分查找一样的一锤子买卖,二分查找的区间划分是为查找数字服务的,并且两端划分出来的状态完全一致(均为不能查找数),因而好像占据了次要地位,且区间无用。但在这里划分出来的区间是主角,两侧划分的状态完全相对(一边能实现一边不能),结果需要通过区间去寻求极值。

按照划分含义来说这里的极值应当是left左边的第一个元素,表明可以求解的区间内最大的一个值,而输出right对应的数只是由于right在二分结束时正好处于left右边第一个数,应当透过这样的取巧明确其本身的含义。

#include <iostream>
#include <algorithm>
using namespace std;

const int MAX = 1e5 + 10;

int arr[MAX];

/*
 * 二分查找
 * n: 数组长度
 * m: 需要划分的份数
 * distance: 当前作为判定对象的距离
 * 目的:判断当前的distance是否能够找到满足条件的解
 */
bool judge(int n, int m, int distance){
    int current = arr[0];
    int number = 1;
    for(int i = 1; i < n; i++){
        if(arr[i] - current >= distance){
            number++;
            current = arr[i];
        }

        if(number >= m){
            return true;
        }
    }
    return false;
}

int main(){
    int n, m;
    while(cin >> n >> m) {
        for (int i = 0; i < n; i++) {
            cin >> arr[i];
        }

        // 排序,二分查找需要有序
        sort(arr, arr + n);

        // 二分,left左侧为可求解,right右侧为不可求解
        int left = 1, right = arr[n - 1] - arr[0];
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (judge(n, m, mid)) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        cout << right << endl;
    }
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值