浅谈二分~~

引入


大家小时候应该玩过这样一个游戏:小伙伴心中想一个1-1000的数,然后另一个小伙伴猜这个数是多少,每次小伙伴回应这个猜的数是比实际的数大还是小,最后肯定能在十次以内找到这个数。在这里就是用到了二分的思想:折半查找,每次找那个数时都取区间的一半,比实际的数要大,就取左边的区间,否则取右边的区间,用代码实现就是:

#include <iostream>
using namespace std;

int main()
{
    int l = 1, r = 1000;  //l表示左端点,r表示右端点,即这个区间可以表示为(0, 1000]
    int res = 45;  //实际的数
    int guess;   //猜测的数
    while(r > l){  //右端点大于等于左端点就停止猜测
        guess = (r+l)/2;   //取中间区间的数,向下取整
        if(guess < res) l = guess+1;  //如果guess比实际的数要小,左端点变为guess的开区间,即变成了(guess, r] 
        else r = guess;
    }
    cout << l << endl;  //输出结果(输出l或r都是一样的)
    return 0;
}

这段代码有两个值得注意和思考的地方,第一:为什么要用左开右闭去表示这个区间,第二:如果判断条件guess < res改为guess <= res会怎么样?第三:关于计算猜的数,要选向上取整还是向下取整?

第二个问题比较简单,因为我们把区间设为了(l, r]这种左闭右开的区间,也就是说,当guess == res时,l = guess+1这步操作跳过了实际的我们要的数res,然后就不能找到答案了。可是第一个问题:为什么要用左开右闭呢?我们可以想象一下这种情况:把l = guess+1改成l = guess后(就是把左开右闭改成左闭右闭的区间),res为1000时,也就是这样:

#include <iostream>
using namespace std;

int main()
{
    int l = 1, r = 1000;  
    int res = 1000;  
    int guess;   
    while(r > l){  
        guess = (r+l)/2;   
        if(guess < res) l = guess;  
        else r = guess;
    }
    cout << l << endl;  
    return 0;
}

运行了一下这个代码,可以发现程序进入了死循环跳不出来,我们查找一下原因:
假设中间的循环到了这一步:l == 999, r == 1000,然后执行第一步,发现guess == 999,然后符合guess < res(res为1000),执行第二步l = guess;,这时终于发现原因了:l == 999,说明又跳回到第一步,区间根本没有被更新。有人可能会说:这是循环第一行代码,就是guess = (r+l)/2;惹的祸,因为这导致了guess向下取整,不能取到1000,把这个代码改成guess = (r+l+1)/2向上取整,然后再把guess < res改成guess <= res这个代码就能运行了。修改代码之后,运行了一下,对于res == 1000是可以的,但是当res == 1时又不行了,这样改行不通。还有一种方案,有的人可能这样改:

#include <iostream>
using namespace std;

int main()
{
    int l = 1, r = 1000;
    int res;
    cin >> res;
    int guess;
    while(r > l){
        guess = (r+l)/2;
        if(guess == l) break;
        if(guess < res) l = guess;
        else r = guess;
    }
    cout << l+1 << endl;
    return 0;
}

这种改法在res == 1时答案错误,并不能根本解决上述的问题。所以最好的方法是把区间改成左开右闭的区间。改成左开右闭的区间后,取整方向是否任意的?不是,这里改成左开右闭时,取整一定要向下取整,也就是guess = (r+l)/2,如果是向上取整,即guess = (r+l+1)/2的话就会造成死循环。原因:当res == 1时,中间的while循环有这样的状态:l == 1, r == 2时,guess == (1+2+1)/2 == 2,因为guess < res,所以r = guess,即r == 2,又变回到之前的状态,所以在左开右闭的区间要改成向下取整。相反,如果是左闭右开的区间就要改成向上取整,代码如下:

    while(r > l){
        guess = (r+l+1)/2;
        if(guess > res) r = guess-1;
        else l = guess; 
        //这个代码还可以改成  
        /*
        if(guess <= res) l = guess;
        else r = guess-1;
        */
    }

应用


传送门

题面:

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

Input
* Line 1: Two space-separated integers: N and C * Lines 2…N+1: Line i+1 contains an integer stall location, xi
第一行:空格分隔的两个整数N和C
第二行—第N+1行:i+1行指出了xi的位置

Output
* Line 1: One integer: the largest minimum distance
第一行:一个整数,最大的最小值

Sample Input
5 3
1
2
8
4
9

题面描述

给出小隔间的编号(位置),然后把牛按一定的方法放进小隔间,要求任意两头牛之间的距离中,最小的那个距离相比于其他把牛放进隔间的方法的最小距离比较,是最大的。

题目分析

这里难就难在理解题意,就拿题目中的样例来说吧:有5个隔间,三头牛,给出了5个隔间的位置,就是:


现在要把三头牛放到这些隔间里面,怎样放才能是符合题意的答案呢?首先我们可以尝试把牛放到1,2,4号隔间,这时任意两头牛的最小距离是1,即任意选两头牛,它们的距离肯定是大于等于1的,这个1就称为任意两头牛的最小距离。我们尝试第二种方法:把牛放到2,4,8号隔间,这时任意两头牛的最小距离是2,2比刚刚的最小距离为1要大,所以舍弃1这个答案。我们再想一想,还有没有比2还要大的摆法?当然有(这不是废话嘛,题目都说答案是3了 )。我们可以尝试把牛放进1,4,8号隔间,这时最小距离是不是3?这个距离3比2和1都要大,而且我们也找不到最小距离比3还要大的摆法了,所以,这个最大的任意两头牛之间的最小距离为3。

那么,现在理解题意之后,有人会问,这跟二分有什么关系?这道题如何运用二分的思想?我们再看回题目:题目中告诉了我们两个隔间最小距离为1,最大距离为1,000,000,000。如果我们从大到小去一个一个验证是否可行,那肯定会超时,所以我们要用到二分的思想来减少运算时间。现在问题转化为如何用二分去解决这道题。我们回顾一下二分的主要代码:

    while(r > l){
        guess = (r+l)/2;
        if(guess < res) l = guess+1;
        else r = guess;
    }

其中:guess < res是我们更新依据,如果猜的数比结果小,更新左端点,否则更新右端点。这道题也是类似,重点就在这个判断依据上面。假如我们已经写了一个交check()的函数,这个函数可以验证你猜的距离guess是否成立(也就是按照你的最小距离放置,奶牛是可以全部放进隔间的),如果成立,就说明答案肯定是大于等于guess的,为什么呢?因为如果答案小于guess,而我当距离为guess时也成立,那么这个最大距离就不是答案,而是guess。所以凡是小于guess的距离都不用去考虑。即:

if(check(guess)) l = guess;

如果guess这个距离不成立的话,那么答案肯定比guess小,因为以guess为最小距离都不能把所有的牛放到隔间,更大的距离当然更加不行。所以拼凑起来就是:

if(check(guess)) l = guess;
else r = guess-1;

很显然,这是个左闭右开的区间,所以记得guess要向上取整。
最后把check()函数搞定就行了。AC代码:

#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 1e6 + 5;
long long a[maxn];
int n, c;

bool check(int m){
    int cnt = c;
    cnt--;
    int t = 0;
    for(int i = 1; i < n; i++){
        if(a[i]-a[t] >= m){
            cnt--;
            t = i;
        }
    }
    return cnt <= 0;
}

int main(){
    cin >> n >> c;
    for(int i = 0; i < n; i++) cin >> a[i];
    sort(a, a+n);
    int l, r;
    l = 1;
    r = 1e9;
    int guess;
    while(r > l){
        guess = (l+r+1)/2;
        if(check(guess)) l = guess;
        else r = guess-1;
    }
    cout << l << endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值