0x04
二分
二分是一种随处可见却非常精妙的算法。二分得基础用法是在单调序列或单调函数中进行查找。因此当问题的答案具有单调性时,就可以通过二分将求解转化成判定(根据复杂度理论判定难度小于求解),这使得二分应用的范围十分广泛。进一步的,我们还可以扩展到通过三分法去解决单峰函数的极值以及相关问题。
1.整数集合上的二分
借助二分,我们把求最优解的问题,转化为给定一个值mid,判断是否存在一个可行方案评分达到mid的问题。
求最小值的最大情况
while(left<=right)
{
int middle=(right+left)/2;
if(check(middle))
{
ans=middle;
left=middle+1;
}
else
right=middle-1;
}
求最大值的最小情况
while(left<=right)
{
int middle=(right+left)/2;
if(check(middle))
{
ans=middle;
right=middle-1;
}
else
left=middle+1;
}
2.实数域上的二分
实数域上的二分比较简单,确定好精度 e p s eps eps,以 l + e p s < r l+eps<r l+eps<r 为循环条件,每次根据在mid上的判定选择l=mid或r=mid分支之一即可。一般需要保留k位小数,则取 e p s = 1 0 − ( k + 2 ) eps=10^{-(k+2)} eps=10−(k+2)。
while(right-left>eps) //答案所需的精度eps
{
double middle=(l+r)/2;
if(check(middle)) right=middle;
else left=middle;
}
3.三分求单峰函数极值
有一类函数被称为单峰函数,它们拥有唯一的极大值点或者唯一的极小值点。
以拥有唯一极大值点的单峰函数f为例,我们在定义域[l,r]内任取两个点 l m i d lmid lmid和 r m i d rmid rmid,把函数分为三段。
1.若 f ( l m i d ) < f ( r m i d ) f(lmid)<f(rmid) f(lmid)<f(rmid),则 l m i d lmid lmid与 r m i d rmid rmid要么同时位于极值点左侧,要么位于极值点两侧。无论哪种情况极值点都在 l m i d lmid lmid右侧,可令 l = l m i d l=lmid l=lmid。
2.同理,若 f ( l m i d ) > f ( r m i d ) f(lmid)>f(rmid) f(lmid)>f(rmid),极大值点一定位于 r m i d rmid rmid左侧,可令 r = r m i d r=rmid r=rmid。
如果我们取 l m i d lmid lmid和 r m i d rmid rmid为三等分点,那么定义域每次缩小1/3。如果我们取 l m i d lmid lmid和 r m i d rmid rmid在二等分点两侧及其接近的地方,那么定义域范围每次近似缩小1/2。通过log级别的时间复杂度即可在指定精度下求出极值。这就是三分法。