二分-最小值最大化问题
大家好,鄙人第一次写CSDN博客,多多关照,大家共同进步!
什么是最小值最大化问题问题呢?我们以一道经典例题为例:
例题
POJ2456
链接
http://poj.org/problem?id=2456
题目描述(原文)
Description
Farmer John has built a new long barn, with N (2 <= N <= 100,000) stalls. The stalls are located along a straight line at positions x1,…,xN (0 <= xi <= 1,000,000,000).
His C (2 <= C <= N) cows don’t like this barn layout and become aggressive towards each other once put into a stall. To prevent the cows from hurting each other, FJ want to assign the cows to the stalls, such that the minimum distance between any two of them is as large as possible. What is the largest minimum distance?
Input
-
Line 1: Two space-separated integers: N and C
-
Lines 2…N+1: Line i+1 contains an integer stall location, xi
Output
- Line 1: One integer: the largest minimum distance
题目大意
沿直线分布的N间房,住C头牛,安排牛的住房,使最近两头牛的距离最大
思考
概念
如果你是第一次看到这样类型的问题,估计也像我一样,没什么头绪。(如果一看就会的我还是orz吧)
当然这道题,我们已经提前收到了一个提示:二分。
但二分和这道题…有啥关系啊?
有! 数学中,二分法的一个基本应用就是函数思想,逼近最值 。
什么意思?这样的语句依旧晦涩难懂。
自己手画一个二次函数图像试试看。给定查找范围 x ∈ [ l , r ] x\in[l,r] x∈[l,r],通过不断的二分, y y y会不断接近最值。
最小值最大化问题,也就是求“最小值”的“最大值”。
所以,按照上面的二次函数的例子,自变量 x x x,就是“最小值”,而 y y y,就是要求“最小值”的“最大值”。
带入题目情境
在题目中,自变量 x x x,就是“最小距离”(minimum distance),而 y y y,就是要求“最小距离”的“最大值”(largest minimum distance)。
按照上面的套路,首先,确定 x x x的可能范围。
假设我安排N头牛中的两头牛住同一间房,那这C头牛之间的最小距离肯定是0(你总不能整个 − 18 c m -18cm −18cm出来吧😏)。所以 l = 0 l=0 l=0。
同样,假设C就等于2,这两头牛肯定放首尾两间才能保证它们之间距离最大。所以 r r r为最远两间房之间的距离。
好。那么答案一定介于 x ∈ [ 0 , a [ n ] − a [ 1 ] x\in[0,a[n]-a[1] x∈[0,a[n]−a[1](即两端点间距离) ] ] ] 之间。
现在就可以开始二分啦!取 x = m i d x=mid x=mid,我们就可以试这个 m i d mid mid是否能使 y y y达到最大。
如果 x = m i d x=mid x=mid是答案,题目中C头牛的任意两头牛之间的距离必须 ≥ m i d \geq mid ≥mid。
于是我们可以进行模拟,将C头牛发配到这N间房。我们设置一个bool check()
函数,来check一下在最小距离为
m
i
d
mid
mid的情况下,能不能把这C头牛都发配完。
贪心思想,首先令一头牛住在左端点,然后扫过去,如果距离dist大于 m i d mid mid,则使此点住一头牛,dist归零。
假设我们要求的标答为 a n s ans ans。
如果C头牛都能装进去,说明最小距离肯定可以是 m i d mid mid,甚至可以大于 m i d mid mid。则 m i d ≤ a n s mid\leq ans mid≤ans。
如果装不进,说明必须继续缩小最小距离来装进所有牛,则 m i d > a n s mid>ans mid>ans。
那么二分的划分条件也出来啦!
这样一直搞下去, m i d mid mid就会无限逼近 a n s ans ans。因为 a n s ans ans只可能是整数,所以肯定有结束条件。
时间复杂度
估计有的小伙伴会担心这个check()
函数会不会拖慢运行时间。在这我可以负责任的讲:不会。
因为,扫一遍check()
只需要
O
(
n
)
O(n)
O(n),二分又是
O
(
l
o
g
x
)
O(log x)
O(logx)的效率,所以总的时间复杂度就只有
O
(
n
l
o
g
x
)
O(nlogx)
O(nlogx)。
结论
总套路,就一句话:二分逼近,从[0,a[n]-a[1]]一直mid即可。
代码
代码被POJ冲掉了😭,但怕有同学不懂我上面说的check()
的意思,我再给两道例题。
NOIP2015 跳石头(洛谷)
POJ1505
这里我就给第一题的代码了,跟上面那道经典例题很像,但有一点不同。也好,给大家一点思考空间。
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std ;
int a[100001],n,m ;
bool check(int x){
int cnt=0,dist=0 ;
for(int i=1;i<=n;i++){
dist+=a[i]-a[i-1] ;
if(dist<x){
if(i==n)return 0 ;
cnt++ ;
}
else dist=0 ;
}
if(cnt>m)return 0 ;
return 1 ;
}
int main(){
int L ;
scanf("%d",&L) ;
scanf("%d%d",&n,&m) ;
for(int i=1;i<=n;i++)scanf("%d",&a[i]) ;
sort(a+1,a+n+1) ;
a[++n]=L ;
int l=0,r=a[n],mid,ans ;
while(l<=r){
mid=(l+r)>>1 ;
if(check(mid))ans=mid,l=mid+1 ;
else r=mid-1 ;
//cout << l << ' ' << r << endl ;
}
printf("%d\n",ans) ;
return 0 ;
}