二分搜索的其他用法

       二分搜索,最常见的是在有序数组中最快地查找某个特定的值,但是二分搜索也有一些别的用处,利用二分搜索的特性,当某个集合按某个特性“有序”时,我们就可以很好地利用二分搜索来找到所需的元素,而不是仅仅局限在最初所学的用途上。

一、基础二分搜索

在讨论别的用途前,先考虑下最基础的用法,也就是如何在一个数列中找到符合条件的值。

来看一道例题:

       1、已知有n个正整数a0, a1, a2, a3,...,和一个正整数k,试问满足ai>k的最小的ai是多少,不存在时输出-1;

       n\leq10^{6}

       0<ai<10^{9}

       0< k< 10^{9}

       样例输入:  ( 第一行为n k)

       7      12

       23    1    6    11   12   8    15

      样例输出:

      15

     思路:利用基本的二分搜索就可以解决。

    cin>>n>>k;
    for (i=0; i<n; i++)
        cin>>a[i]; 
    sort(a, a+n);     //升序排序

    int l=-1, r=n;    //左开区间,右闭区间
    while (r-l>1)    
    {
        int mid=(l+r)/2;
        if (a[mid]>k)
            r=mid;
        else
            l=mid;
    }

    if (a[l]>k)
        cout<<a[l]<<endl;
    else
        cout<<-1<<endl;

上面这种算法就是二分搜索算法,STL提供了lower_bound函数实现二分搜索

 

二、二分查找

        根据二分搜索的特性,我们可以得到一个结论,二分查找可以在有序集合中快速找到所要的一个元素,重点在有序上,对于上面的用法,我们二分的依据是“大于”,也就是说对于一个符合“大于”条件的元素,其右侧所有元素都必定符合这个条件,相反,不符合“大于”条件的元素的左侧所有元素都不会符合这个条件,这就是一种有序。

        如果我们将这个判断条件稍微复杂化,就可以得到另一种用途,先看道例题:

       poj 2456     题目大意是:

     农夫John有一间N(2 <= N <= 100,000) 个畜栏的牛舍,这些畜栏排在一条直线上,它们的位置是x1,...,xN (0 <= xi <= 1,000,000,000)。他的C(2 <= C <= N)头牛对牛舍很不满意,所以John为了防止牛相互打架,所以想给牛分配这些畜栏,一头牛占一个畜栏,希望最大化最近两头牛之间的距离。所以可以得到的最大的最近两头牛的距离是多少?

      输入样例:

      N=  5     C= 3
      Xi= 1    2    8    4    9

     输出样例:

     3

思路:

     (1)可以发现,如果我们将最近两头牛之间的距离视为一个数列,既这个数列为 {1, 2, 3, 4, 5,.......,1000 000 000 },然后分析二分查找所需的条件。可以发现如果可以得到某个距离D,既至少有一种分配方法可以使最近距离大于等于D。那么在上面那个数列中,D左侧所有值都是成立的;相反,如何D无法被得到,那么D右侧所有值都不会成立。

     (2)判断距离D是否可以实现,只需从左向右,距离大于等于D时的畜栏放一头牛,既贪心算法就可以实现。

       所以,对答案进行二分,也就是对1~1 000 000 000所有整数进行二分,每次取mid值进行判断,最终剩下的一个值就是所需的最大的最近距离。

      二分的判断条件:  check(D) :  可以按某种方式分配畜栏,使相邻两头牛最近距离为D,并返回true。

代码如下:

#include<iostream>
#include<cstdio>
#include<algorithm>
#define MAX 1e9
using namespace std;
int n, c;
int x[100002];

bool check(int d)
{
    int temp=c-1;
    int flag=x[0];
    int i;
    for (i=1; temp>0&&i<n; i++)
    {
        if (x[i]-flag>=d)
        {
            temp--;
            flag=x[i];
        }
    }
    if (temp>0)
        return false;
    return true;
}

int main()
{
    while (~scanf("%d %d", &n, &c))
    {
        int i=0;
        for (i=0; i<n; i++)
            scanf("%d", &x[i]);
        sort(x, x+n);

        //对1~1,000,000,000进行二分搜索
        int l=1, r=MAX+1;      //左闭区间,右开区间
        while (r-l>1)
        {
            int mid=(l+r)/2;
            if (check(mid))
                l=mid;
            else
                r=mid;
        }

        cout<<l<<endl;
    }
    return 0;
}

 

有时需要二分的元素或者集合比较难以发现,比如下面这个最大化平均值的问题:

        有n(1<=n<=10,000)个物品的重量和价值分别是wi和vi(1<=wi,vi<=1,000,000)。从中选出k(1<=k<=n)个物品使得单位重量的价值最大,输出单位重量的价值。

思路:

显然,如果将每个物品的单位价值分别计算出来,再排序进行从大到小选取,结果是不正确的。但这个问题可以用二分查找解决,于是主要问题就是如何设置需要二分的元素的集合和二分的条件。

(1)首先像上一道题一样,对答案进行二分,二分的区域就是最终结果的取值范围。

(2)继续按套路,很容易得到二分条件:

         check(x):可以选择k个物品使得单位重量的价值不小于x

        该check(x)函数满足如下这个条件,对k个物品的集合进行讨论

        \sum vi/\sum wi\geqslant x

        可以发现这个不等式很难在尽量少的时间内计算出来,因此可以将它化简一下

        \sum vi\geqslant \sum (x\times wi)

        \sum \left ( vi-x\times wi \right )\geqslant 0

        因此,我们只需要把每个物品的 \left ( vi-x\times wi \right )计算出来,并进行排序,选出最大的k个,如果它们的和不小于0,那么check(x)就会返回true。

       最后,在进行二分的时候,因为这个问题的结果是double类型的,而为了提高结果的准确性,可以将二分的循环条件改为循环次数,而不是上下界的关系。

三、总结

       对答案进行二分 + 实现二分条件

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值