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