二分搜索,最常见的是在有序数组中最快地查找某个特定的值,但是二分搜索也有一些别的用处,利用二分搜索的特性,当某个集合按某个特性“有序”时,我们就可以很好地利用二分搜索来找到所需的元素,而不是仅仅局限在最初所学的用途上。
一、基础二分搜索
在讨论别的用途前,先考虑下最基础的用法,也就是如何在一个数列中找到符合条件的值。
来看一道例题:
1、已知有n个正整数a0, a1, a2, a3,...,和一个正整数k,试问满足ai>k的最小的ai是多少,不存在时输出-1;
n
0<ai<
0 k
样例输入: ( 第一行为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个物品的集合进行讨论
可以发现这个不等式很难在尽量少的时间内计算出来,因此可以将它化简一下
因此,我们只需要把每个物品的 计算出来,并进行排序,选出最大的k个,如果它们的和不小于0,那么check(x)就会返回true。
最后,在进行二分的时候,因为这个问题的结果是double类型的,而为了提高结果的准确性,可以将二分的循环条件改为循环次数,而不是上下界的关系。
三、总结
对答案进行二分 + 实现二分条件