{ id : 'top-progress-bar', // 请勿修改该值 color : '#77b6ff', height : '2px', duration: 0.2, }

最大值最小与最小值最大问题(二分)

二分逼近思想

•对于难以直接确定解的问题,采取二分枚举+检验的思想.
•已知解为x,验证x是否满足要求.
•如果答案具有特定的范围,并且验证答案是否成立的函数具有单调性。则可以在范围内对答案进行二分验证,从而快速确定答案。

对于答案判断:

在二分答案的时候需要判断,从而确定下一个范围。
可以用一个bool Check(x)函数来判断,返回true表示满足,返回false表示不满足.
可以类比数学函数f(x)>=0和f(x)<0来理解.
根据具体问题写出相应的Check函数往往就是解决问题的关键点。

具体总结如下:

/*
//二分查找总结:由于本人二分常年写成死循环

简介:二分查找 == 折半查找
要求:线性表,有序表(注意升序与降序)
思想:
	设查找区间[L,R]
	取中点 mid = (L+R)/2
	判定mid是否符合要求:(如何判断:bool check(int mid); )
		是:缩短区间求边界;直接返回;
		否:缩短区间
	最终结果val = R 或者val = L;
*/
 
//典型线性表查找数据
int er_search(int a[],int n, int key)
{
	const int inf = 0x3f3f3f3f;
	
	int L=0,R=n;
	while(L<R){
		int mid = (L+R)/2;
		if(key==a[mid]){
			return mid;
		}
		else if(key<a[mid]){
			R=mid-1;
		}
		else if(k>a[mid]){
			L=mid+1;
		}
	}
	return inf;
} 
//CSDN某博客对二分的64种分类
/*
*取整方式
	向下取整(最小值) 向上取整(最大值)
	
*区间开闭
	闭区间 左闭右开区间 左开右闭区间 开区间
	
*问题类型
	单增
	对于不下降序列a,求最小的i,使得a[i] = key 
	对于不下降序列a,求最大的i,使得a[i] = key 
	对于不下降序列a,求最小的i,使得a[i] > key 
	对于不下降序列a,求最大的i,使得a[i] < key 
	单减
	对于不上升序列a,求最小的i,使得a[i] = key 
	对于不上升序列a,求最大的i,使得a[i] = key 
	对于不上升序列a,求最小的i,使得a[i] < key 
	对于不上升序列a,求最大的i,使得a[i] > key
*/
 
//下面四个不下降的例子
//a[] = 1 2 3 4 5 6 6 6 7 9 
//min i,a[i] = key;  =>a[5]
    while(s < e){
        mid = (e+s)/2;// 向下取整
        if(key <= a[mid])
            e = mid;
        else
            s = mid + 1;
    }
 
//max i,a[i] = key =>a[7]
    while(s < e){
        mid = (e+s+1)/2;// 向上取整
        if(key >= a[mid])
            s = mid;
        else
            e = mid - 1;
    }
 
//min i, a[i] > key =>=>a[8]
    while(s < e){
        mid = (e+s)/2;//向下取整
        if(key < a[mid])
            e = mid;
        else
            s = mid + 1;
    }
 
// max i, a[i] < key =>a[4]
    while(s < e){
        mid = (e+s+1)/2;//向上取整
        if(key > a[mid])
            s = mid;
        else
            e = mid - 1;
    }
 
/*巧记,但不是完全正确
	循环:L<R 
	求mid时:求max :L+R+1  求min: L+R;
	if():真实值与猜测值的关系作为条件:max-真实大于猜测 min-真实小于猜测
	防死循环:调整if下的L或者R 另一个边界在else下注意+-1;
	
	总结:循环L<R mid注意1 else下防死循环
*/
//另一种简单分类:第一个大于v,第一个大于等于v,最后一个小于v,最后一个小于等于v
/*内容来自:http://www.cnblogs.com/xiaowuga/p/8604750.html
	第一个大于等于v:
		lower_bound(ForwardIterator first, ForwardIterator last,const T& val, Compare comp)
		
		我们假设L为当前区间的答案,R为当前区间的实际答案(因为R是第一个大于等于v的下标),
		我们每次二分的实际上是为了让L和R不断靠近,
		所以当L==R的时候,我们假设的答案等于实际的答案,那么就结束循环了,返回答案L。
		while(L<R){
            int M=(L+R)/2;
            if(a[M]>=v) R=M;//R是第一个大于等于v下标,那么R最大只能是m
            else L=M+1;//[M,R)区间内的下标都是小于v的,L作为最后的答案最小只能是M+1
        }
	
	第一个大于v:
		upper_bound(ForwardIterator first, ForwardIterator last,const T& val, Compare comp);
        
		我们假设L为当前区间的答案,R为当前区间的实际答案(因为R是第一个大于v的下标),
		我们每次二分的实际上是为了让L和R不断靠近,
		所以当L==R的时候,我们假设的答案等于实际的答案,那么就结束循环了,返回答案L。
		while(L<R){
            int M=(L+R)/2;
            if(a[M]>v) R=M;//R是第一个大于v下标,那么R最大只能是M
            else L=M+1;//[M,R)区间内的下标都是小于等于v的,L作为最后的答案最小只能是M+1
        }
			
	最后一个小于等于v	
	
		我们假设R为当前区间的答案,L为当前区间的实际答案(因为L是最后一个小于等于v的下标),
		我们每次二分的实际上是为了让L和R不断靠近,
		所以当L==R的时候,我们假设的答案等于实际的答案,那么就结束循环了,返回答案L。
		while(L<R){
            int M=(L+R+1)/2;
            if(a[M]<=v) L=M;//L是最后一个小于等于v下标,那么L最小只能是M。
            else R=M-1;//(L,M]区间内的下标都是大于v的,R作为最后的答案最大只能是M-1。
        }
			
	最后一个小于v
	
		我们假设R为当前区间的答案,L为当前区间的实际答案(因为L是最后一个小于v的下标),
		我们每次二分的实际上是为了让L和R不断靠近,
		所以当L==R的时候,我们假设的答案等于实际的答案,那么就结束循环了,返回答案L。
		while(L<R){
            int M=(L+R+1)/2;
            if(a[M]<v) L=M;//L是最后一个小于v下标,那么L最小只能是M。
            else R=M-1;//(L,M]区间内的下标都是大于等于v的,R作为最后的答案最大只能是M-1。
        }		
*/
 
//二分条件搜索的应用:最大化最小值,最小化最大值,最大化平均值
/*最大化最小值
	边界属性: 	正确答案ans
				满足条件但不是最大:ans-1
				不满足条件:ans+1
				
				二分类型:最后一个满足条件的ans值
					思路:	把条件看成具体的值
							求最后一个小于等于v
*/
/* 最小化最大值
	边界属性: 	正确答案ans
				满足条件但不是最小:ans+1
				不满足条件:ans-1		
				二分类型:第一个满足条件的值
					思路:	把条件看成具体的值
							求第一个大于等于v
*/	
 
/*最大化平均值:
模型构建:
有n个物品,每个物品分别对应一个重量w和价值v。要求选出k个,使得平均每单位重量的价值最大。
二分模型:搜索满足条件check(mid)的最大解
check()模型:
构建过程:
	单个物品:
		平均价值:v/w
		判断这个物品是否被挑选(是否满足单位价值mid) v/w>=mid
			化简得 v-wx>=0; 可记录c[i] = v-wx(第i个)
	多个物品:
		循环所有物品
		
	k个物品
		求和c[0]--c[k-1]
		如果这个和大于零,说明总体的平均值大于mid
	
模型:二分查找最大化平均值
int n;数据组数
int k;要求件数
bool check(double mid){
	
	c[i] = v[i]-mid*w[i];
	
	sort(c,c+n,cmp);//逆序
	
	double sum=0;
	sum+=c[0] ---c[k-1]
	
	return sum>=0;//sum = 0时候是正好满足条件
}
*/

再深化

最大化最小值(二分值最小越小越容易满足条件,求最大值)

区间长度为1时的写法:
解的范围为 

// 计算区间为[lb, rb]
while( rb > lb )  // 区间长度为1时终止循环
{
    // 防止溢出
    int m = lb + (rb - lb + 1) / 2;    // 由于是区间长度为1时终止循环,所以要加1
    if( ok(m) ) lb = m;
    else rb = m - 1;
}
// 跳出循环时 lb == rb

区间长度为2时的写法:
解的范围为 
while( rb - lb > 1 )  // 区间长度为2时终止循环
{
    // 防止溢出
    int m = lb + (rb - lb) / 2;    // 由于是区间长度为2时终止循环,所以不用加1(不会死循环)
    if( ok(m) ) lb = m;
    else rb = m;
}
// 跳出循环时 lb + 1 == rb
// 答案为 lb
最小化最大值(二分值越大越容易满足条件,求最小值)
区间长度为1时的写法:
解的范围为 

while( rb > lb )
{
    // 防止溢出
    int m = lb + (rb - lb) / 2;     // 这里虽然区间长度为1,但不需要加1(不会死循环)
    if( ok(m) ) rb = m;
    else lb = m + 1;
}
// 跳出循环时 lb == rb
区间长度为2时的写法:
解的范围为 
while( rb - lb > 1 )
{
    // 防止溢出
    int m = lb + (rb - lb) / 2;
    if( ok(m) ) rb = m;
    else lb = m;
}
// 跳出循环时 lb + 1 == rb
// 答案为 rb
浮点数的二分,100次循环可以达到2^-100(约为10^-30)的精度范围

以最大化最小值为例(即小于该数的解均满足条件)

for( int i = 0; i < 100; ++ i )
{
    double m = (lb + rb) / 2;
    if(check(m)) lb=m;
     else rb=m;
}
// 跳出循环时 lb 与 rb 近似相等,所以都可作为答案

以上内容转自网络

常用模板

//最大值最小
int l,r;
while(l<r)
{
int mid=l+r+1>>1;//(l+r)/2
if(check(mid)) l=mid;
else r=mid-1;
}
//最小值最大
int l,r;
while(l<r)
{
int mid=l+r>>1; //(l+r)/2
if(check(mid)) r=mid;
else l=mid+1;
}

1.题目链接
2.题意概述:
农夫有c头牛,n个隔间,c头牛很躁动,很容易相互打架,因此农夫想把它们分得越远越好,要你分配隔间使得相邻两头牛的距离越远越好,问你这c头牛分割的最小距离的最大值。
3.解题思路: 
先对隔间的坐标排序,对于牛,最小距离是0,最大距离不会超过两端两头牛的距离值,因此二分地查找分割距离的最大值,每次mid都judge一次,judge的时候贪心地放置牛,保证前i头牛是符合这样分割标准的。

#include<iostream>
#include<stdlib.h>
#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxn=1e5+10;

int a[maxn];
int n,c;

bool check(int mid)
{
	int last=0,cnt=1;
	for(int i=1;i<n;i++)
	{
		if(a[i]-a[last]>=mid)
		{
			cnt++;
			last=i;
		}
	}
	if(cnt>=c)return true;
	else return false;
}

int main()
{
	ios::sync_with_stdio(false);
	cin>>n>>c;
	for(int i=0;i<n;i++)
	{
		cin>>a[i];
	}
	sort(a,a+n);
	int l=0,r=a[n-1];
	while(l<=r)
	{
		int mid=l+r>>1;
		if(check(mid))l=mid+1;
		else r=mid-1;
	}
	cout<<r<<endl;
	return 0;
}
posted @ 2021-04-09 22:18  星空Dreamer  阅读(470)  评论(0)    收藏  举报