最大值最小与最小值最大问题(二分)
二分逼近思想
•对于难以直接确定解的问题,采取二分枚举+检验的思想.
•已知解为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;
}

浙公网安备 33010602011771号