二分搜索算法笔记C++

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<cstdio>
#include<algorithm>
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;
}

 

posted @ 2018-12-20 15:14  Dr_Lo  阅读(259)  评论(0编辑  收藏  举报