二分搜索求最小化的最大值、最大化的最小值

二分搜索

我们都大致知道二分查找是怎样的,大概就是取左右的中点与要查找的值相比较,如果比中点小,那范围就可以缩小到左边了,瞬间去掉了一般的数据,那通过这个思想,首先我们来“猜”一个值作为所要求的答案(最大化的最小值或最小化的最大值),也就是当作要查找的值,假设他是正确答案,然后我们通过逐步的缩小范围最后找到正确答案。
如果遇到题目要求最小的最大值或者使得最小值最大化,这个时候一般情况枚举很容易超时,这时我们就可以用二分搜索的思想来解决问题。

1.最小化最大值


我们通过这个题目来理解理解

建立火车站
新冠疫情,导致了各个城市之间物资输送的障碍。假设有N个城市在一条直线上,为了物资能顺利抵达各个城市,可以在路线上建立最多个数为K个暂时停靠站,由于火车在两个站台(城市也算站台)之间的距离越近,需要的总花费越少,因此我们需要让火车相邻两个站台之间的最大距离L最小,求出距离L,2 ≤N ≤100000, 0 ≤K ≤100000,所有城市坐标小于等于10^12,且不存在负值。提醒: 城市坐标均为正整数,且停靠站只能建在整数坐标点上。

输入描述:
第一行输入城市个数N,可建立停靠站个数K, 第二行输入N个城市的坐标(不保证前一个城市坐标比后一个城市小)。

输出描述:
输出L

示例1

输入
2 2
4 106

输出
34


1)递归写法:

#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
ll a[100005];
ll n,k,ans;

ll bs(ll l,ll r) {
    ll mid=(l+r)/2;  //mid就是一开始随便猜的那个答案,先认定他是对的,不用担心,他会逐渐接近正确答案
    ll sum=0;
    if(l==r) return l; //递归出口
    for(int i=1;i<n;i++) {
        ll deta=a[i]-a[i-1]; //每相邻的两个站之间的距离
 //即使是求最小化的最大值,那也是最大值,如果出现了比这个值还大的,那就往里面塞车站,让它变小
        if(deta>mid)  
            sum+=(deta-1)/mid; //间距是所猜值的n倍,那就*至少*需要塞n-1个车站在中间
    }
    if(sum>k) return bs(mid+1,r); //需要塞的车站数比给的多,说明猜小了,所以范围移到了更大的右半部分
    return bs(l,mid); //猜大了
}

int main () {
    cin>>n>>k;
    for(int i=0;i<n;i++) 
        cin>>a[i];
    sort(a, a+n);  //二分搜索前提是有序
    ans=bs(1,1e12); //其实就是先随便猜了一个比较大的数字
    cout<<ans;
}

2)非递归写法:

#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
const int N=1e5+5;
ll a[N];
int n,m;
bool check(ll mid){
	ll sum=0;
	for(int i=1;i<n;i++){
		sum+=(a[i]-a[i-1]-1)/mid;
		if(sum>m) return false; //猜小了
	} 
	return true; //猜大了
}
ll bsearch(ll l,ll r){
	while(l<r){
		ll mid=(l+r)/2;
		if(check(mid)) r=mid;  //大了,所以移到左半部分
		else l=mid+1; //小了,右半部分
	}
	return l;
}
int main(){
	cin>>n>>m;
	for(int i=0;i<n;i++) cin>>a[i];
	sort(a,a+n);
	cout<<bsearch(1,a[n-1]);
	return 0;
}

2.最大化最小值

OpenJudge 2456 Aggressive cows
题目意思:农民约翰有用C只牛,然后他有N个隔间,每个隔间都有自己的坐标位置(一维的)pos,如何安排把牛安排进隔间才能使,所有牛之间距离的最小值最大,我们不需要求这个分配方案,我们只需要求这个最小距离的最大值,很裸的最小值最大化。

#include "iostream"
#include "algorithm"
#include "cstdio"
using namespace std;

const int N = 1e5 + 10;
int a[N];
int n, c;

bool check(int dis) { /// 在模拟放置的时候为了放置的尽可能的稀疏,因此从第一个位置开始放
    int cnt = 1; /// 记录成功放置的个数
    int last = a[0]; /// 记录上一次放置的位置
    for (int i = 1; i < n; ++i) {
        if (a[i] - last >= dis) { /// 当期位置和上一次放置位置相差大于或等于dis则当前位置可以放
            last = a[i];
            cnt++;
        }
    }  
    if (cnt >= c) return true; //大于说明可以放的牛多了,也就是“猜”的距离小了,范围往右边移
    else return false;
}

int bsearch(int l,int r) {
    int ans;
    while (l <= r) {
        int mid = (l + r) / 2;
        if (check(mid)) { /// 小了所以要往大取
            ans = mid; /// 记录每次可以放置的值,最后一次便是最优解
            l = mid + 1;
        } else {
            r = mid - 1;
        }
    }
    return ans;
}

int main() {
    cin >> n >> c;
    for (int i = 0; i < n; ++i) {
        cin >> a[i];
    }
    sort(a, a + n); /// 输入的位置是无序的,先排序方便模拟检验
    int l = 1, r = a[n - 1] - a[0];
    int ans = bsearch(l,r);
    cout << ans << "\n";
    return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
"最大化最小值"或"最小化最大值"问题的函数曲线一般是非常复杂的,因为目标函数可以是任意的函数。但是,我们可以通过一个简单的例子来理解这类问题的函数曲线。 假设我们要在一个一维数组中到一个最大的数 x,使得数组中所有数都不小于 x。我们可以将这个问题转化为一个函数 f(x) 的形式,其中 f(x) 表示 "数组中所有数都不小于 x" 这个条件是否成立。具体地,如果数组中存在一个数小于 x,则 f(x) 为 false,否则 f(x) 为 true。 这个函数的曲线是一个阶梯状的函数,如下图所示: ``` | | | | | | | | |___|___|___|___ x1 x2 x3 x4 ``` 其中,每个竖直的线段表示一个数组元素,x1、x2、x3、x4 分别表示四个元素的值,每个水平的线段表示函数值为 true 的区间。例如,当 x 取值在 [x3, x4] 区间内时,f(x) 的值都为 true,因为数组中所有元素的值都不小于 x3。 在这个例子中,我们要到的最大的 x,就是最后一个函数值为 true 的点所对应的 x 值,即 x4。这个问题可以通过二分查法解决,每次取中间值,判断中间值是否满足条件,然后不断缩小搜索区间,最终到最大的 x 值。 类似的,对于"最小化最大值"问题,我们可以构造一个类似的函数,表示所有满足条件的最大值是否小于等于 x。这个函数的曲线也是一个阶梯状的函数,但是是逆向的,即从右上方向左下方延伸。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值