* 二分搜索法,是通过不断缩小可能存在的范围,从而求得问题最优解的方法。
下面介绍经典的二分搜索法问题:
一.从有序数组中查找某个值
lower_bound
给定长度为n的单调不下降数列a0····an-1和一个数 k,求满足ai ≥ k 条件的最小的i 。不存在的情况下输出n。
1 ≤ n ≤ 10^6
0 ≤ a0 ≤a1 ≤ ···≤an-1≤ 10^9
0 ≤ k ≤ 10^9
输入:
n = 5
a = { 2, 3 , 3 , 5 , 6 }
k=3
输出:
1( a0 < 3 , a1 >= 3)
分析:如果朴素地按照顺序逐个查找的话,也可以求得答案,但是当数列长度很长时,这种方法显然很费时。但是如果利用数列的有序性这一条件,则可以得到更高效的算法。首先先看一下第n/2个值,如果a [n/2] ≥ k的话,就可以知道解不大于n/2。反之,如果 a [n/2] ≤ k ,就可以知道解大于n/2,通过这一次的比较,可以把解的范围缩小一半。反复与区间的中点进行比较,这样就可以不断地把范围缩小到原来的一半,最终在O(logn)次的比较之内求得最终的解。
思想转化为代码:
//输入
int n,k;
int a[MAX_N];
void solve()
{
int lb = -1 , up = n;//初始化接的存在范围 lb(下界), ub(上界)
while(ub-lb > 1) //重复循环知道解的范围不大于1
{
int mid = (lb+ub)/2;
if(a[mid]>=k) // 如果mid满足条件,则解的存在范围变为(lb,mid]
ub = mid;
else // 如果mid不满足条件,则解的存在范围变为(mid,ub]
lb = mid;
}
printf("%d",ub);
}
这种算法被称为二分搜索。此外,STL以lower_bound函数形式实现了二分搜索。这个算法除了在有序数列查找值之外,在求最优解的问题上也非常有用。
模板题:
让我们考虑一下“求满足某个条件C(x)的最小值x”这一问题。对于任意满足C(x)的x,如果所有x' ≥ x 也满足C(x)的值,我们就可以用二分搜索来求得最小的x。
首先我们将区间的左端点初始化为不满足C(x)的值,右端点初始化为满足C(x)的值。然后每次取中点mid= ( lb + ub ) / 2 ,判断C(mid)是否满足并缩小范围,直到( lb , ub ]足够小了为止,最后ub就是要求的最小值。最大化的问题也可以用同样的方法求解。
二. 假定一个解判断是否可行
Cable master POJ - 1064
//输入
int N,K;
double L[MAX_N]
int C(int x) // 判断是否满足条件
{
int num;
for(i=0;i<n;i++)
num += (int)(L[i]/x);
return num >= K; //大于等于K返回1,否则返回0
}
void solve()
{
double lb=0,ub=INF;//区间初始化
for(int i=0;i<100;i++)
{
double mid=(lb+ub)/2;
if(C(mid))
lb=mid;
else
up=mid;
}
printf("%.2f\n",floor(ub*100)/100); //floor函数:向下取整
二分搜索法的结束判定:
在输出小数的问题中,一般都会指定允许的误差范围或者是指定输出中小数点后面的位数。因此在使用二分搜索法时,有必要设置合理的结束条件来满足精度的要求。在上面的程序中,我们指定了循环次数作为终止条件。1次循环可以把区间的范围缩小一半,100次的循环则可以达到10^-30的精度范围,基本上是没有问题的,除此之外,也可以把终止条件设为像(ub-lb)>EPS这样,制定一个区间的大小。在这种情况下,如果EPS取得太小了,就有可能会因为浮点小数精度的原因导致陷入死循环,请千万小心。像这样,如果在求解最大化或最小化问题中,能够比较简单的判断条件是否满足,那么使用二分搜索法就可以很好的解决问题。
三.最大化最小值
Aggressive cows
POJ - 2456Description
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?
Input
* Line 1: Two space-separated integers: N and C* Lines 2..N+1: Line i+1 contains an integer stall location, xi
Output
* Line 1: One integer: the largest minimum distance
Sample Input5 3
1
2
8
4
9
Sample Output
3
题意:农夫约翰搭了一间有N间牛舍的小屋。牛舍排在一条线上,第 i 号牛舍在 xi 的位置。但是他的 M 头牛对小屋很不满意,因此经常互相攻击。约翰为了防止牛之间互相伤害,因此决定把每头牛都放在离其他牛经可能远的牛舍。也就是要最大化最近的两头牛之间的距离。
分析:类似的最大化最小值或者最小化最大值的问题,通常用二分搜索就可以很好的解决 。套用模板:
令C(X): 可以安排牛的位置使得任意的两头牛的距离不小于d
问题:求满足C(d)的最大的d
区间初始化:lb=0,ub=INF
思想转化为代码:
//输入
int N,M;
int x[MAX_N];
bool C(int d)
{
int last = 0;
for(int i =1 ;i < M ;i++)//放M头牛
{
int crt = last + 1;//crt
while(crt < N && x[crt]-x[last]<d)
crt++;
if(crt == N) return false;放到N为止,说明这M头牛放不下去
last = crt ;
}
return true;
}
void solve()
{
sort(x,x+N); //对x数组排序
int lb = 0; ub = INF; //区间初始化
while(ub-lb > 1)
{
int mid = (lb+ub) / 2;
if(C(mid)) //如果可以放下M头牛,说明当前的x有点小,让x变大再判断
lb=mid;
else //如果放不下,说明当前的x有点小,就先让x变小再判断
ub=mid;
}
printf("%d",lb);
}