1.背景
1.1 结论
在有序数组中查找某个值,或者在求最优解问题时,二分搜索非常有用。思想一般是先假定一个解,并判断是否可行,接着缩小解的范围继续判断。
1.2 概念
二分搜索法,是通过不断缩小解可能存在的范围,从而求得问题最优解的方法。在程序设计竞赛中,经常可以见到二分搜索法和其他算法结合的题目。
2.二分搜索
2.1思想
二分答案转化为判定。一个宏观的最优化问题也可以抽象为函数,其“定义域”是该问题下的可行方案,最这些可行方案进行评估得到的数值构成函数的“值域”,最优解就是评估最优的方案,(不妨设评分越高越优)。假设最优解评分为S,显然对于所有大于S的值,都不存在一个合法的方案达到该评分,否则就与S的最优性矛盾;而对于所有< S 的值,一定存在一个合法的方案达到或者超过该评分,因为最优解就满足这个条件。这样问题的值域就具有一种特殊的单调性——在S的一侧合法、另一侧不合法,可以通过二分找到这个分界点S。借助二分,我们把求最优解问题,转化为给定一个值mid,判定是否存在一个可行的方案评分达到mid的问题。
2.2例题
2.2.1题意简述:有N本书排成一行,已知第 i 本书的厚度是Ai。把它们分成连续的M组,使T最小化,其中T表示厚度之和最大的一组的厚度。
2.2.2例题解析:题目出现了类似“最大值最小”的含义,可以看出答案具有单调性(也就是说这个答案满足在一定单调区间内)。所以可以使用二分搜索,而判定函数如何写呢?假设最大组为size,一共有m组,那么m*size一定小于所有厚度之和;那我们顺序用size减去每本书的厚度,看看能分为几组和小于size且最大,这样一来如果组数小于m,那我们可以通过将一组分割成好几组来凑到m,此时最大厚度不变。而如果组数大于m,我们则可以通过合并来使组数等于m,但这样一来最大厚度就必然大于size。而答案必然存在于1到inf(无穷大),故我们只需找到临界点,即为答案。
2.2.3 代码示例:
bool check(int size){
int cnt = 1;
int tmp = size;
for(int i = 0;i < n;i++){
if(tmp - a[i] >= 0) tmp -= a[i];
else cnt++,tmp = size - a[i];
}
return cnt <= m;
}
int main(){
scanf("%d%d",&n,&m);
for(int i = 0;i < n;i++) scanf("%d",a+i);
int l = 0,r = inf;
while(l < r){
int mid = (r + l )/2;
if(check(mid)) l = mid + 1;
else r = mid;
}
printf("%d\n",l);
return 0;
}
2.3 最大化平均值
上面的例题可以用“最小化最大值”来形容,当然也有“最大化最小值”,不过大同小异,二分搜索还有一种应用,用来解决最大化平均值问题。
有n个物品的重量和价值分别是 wi 和 vi 。从中选出 k 个物品使得单位重量的价值最大。
2.3.1 例题解析:上述问题贪心策略是解决不了的,因为有俩个权值啊。对于这个问题是可以用二分搜索来解决。首先定义:
条件C(x) = 可以选择使单位重量的价值不小于x。
因此原问题就成了求满足C(x)的最大x。而:
- 价值和/重量和>=x
- 价值和-重量和*x>=0
- 和(价值-重量*x)>=0
- 可以对(价值-重量*x)的值进行贪心的选取,选取最大的k个 和>=0
2.3.2 代码示例:
#include
#include
using namespace std;
const int maxn = 10010;
const int inf = 1e9+7;
double w[maxn],v[maxn],y[maxn];
int n,k;
bool check(double size){
for(int i = 0;i < n;i++)
y[i] = v[i] - w[i]*size;
sort(y,y+n);
double sum = 0;
for(int i = n-1;i >= n-k;i--){
sum += y[i];
}
return sum >= 0;
}
int main(){
scanf("%d%d",&n,&k);
for(int i = 0;i < n;i++) scanf("%lf%lf",w+i,v+i);
double l = 0,r = inf;
for(int i = 0;i < 100;i++){
double mid = (l + r)/2;
if(check(mid)) l = mid;
else r = mid;
}
printf("%.2f",r);
return 0;
}