前言
三分不在 CSP 考纲内,但是还是挺有用的。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
正文
作用
最简单的,三分函数可以求单峰函数[1]的最大值。[2]
这时候学过一些高级货的同学就要问了:为什么不通过求导函数零点来求极值点?
> 客观上,求出导数后,通过二分法求出导数的零点(由于函数是单峰函数,其导数在同一范围内的零点是唯一的)得到单峰函数的极值点是可行的.
> 但首先,对于一些函数,求导的过程和结果比较复杂.
> 其次,某些题中需要求极值点的单峰函数并非一个单独的函数,而是多个函数进行特殊运算得到的函数(如求多个单调性不完全相同的一次函数的最小值的最大值).此时函数的导函数可能是分段函数,且在函数某些点上可能不可导.
> -- OI Wiki[3]
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
过程
我们令当前求解的区间为 [l,r][l,r][l,r] ,求解要求精度至少为 epsepseps。[4]
在区间内任取[5]两点 lmid,rmidlmid,rmidlmid,rmid 。
如果 f(lmid)<f(rmid)f(lmid)<f(rmid)f(lmid)<f(rmid),则有两种可能:
1. 极值点在 rmidrmidrmid 右侧,即在 [lmid,rmid][lmid,rmid][lmid,rmid] 内 f()f()f() 的值单调递增;
2. 极值点在 [lmid,rmid][lmid,rmid][lmid,rmid] 中。
对于这两种情况,显然极值点不可能存在于 [l,lmid)[l,lmid)[l,lmid) 区间内,所以可以直接令 l=lmidl=lmidl=lmid 舍去这一区间。
当 f(lmid)>f(rmid)f(lmid)>f(rmid)f(lmid)>f(rmid) 时,也是两种可能:
1. 极值点在 lmidlmidlmid 右侧,即在 [lmid,rmid][lmid,rmid][lmid,rmid] 内 f()f()f() 的值单调递减;
2. 极值点在 [lmid,rmid][lmid,rmid][lmid,rmid] 中。
显然对于这两种情况,(rmid,r](rmid,r](rmid,r] 区间内不可能存在极值点,我们直接令 r=rmidr=rmidr=rmid 舍去该区间。
> 由于三分法一般求的极值是小数,所以不用考虑等于,只需要把 epsepseps 拉高就行。
当 r−l<epsr-l<epsr−l<eps 时,误差已经小到满足题目要求,可以直接输出 lll。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
例题
P1883 【模板】三分 / 函数 / [ICPC 2010 Chengdu R] Error Curves / 无ACGO版本
题目大意
给出 nnn 个二次函数 f1(x),f2(x),⋯ ,fn(x)f_1(x),f_2(x),\cdots ,f_n(x)f1 (x),f2 (x),⋯,fn (x)(形如 ax2+bx+cax^2+bx+cax2+bx+c ),定义F(x)=max(f1(x),f2(x),⋯ ,fn(x))F(x)=max(f_1(x),f_2(x),\cdots ,f_n(x))F(x)=max(f1 (x),f2 (x),⋯,fn (x)) ,求 FFF 在 [0,1000][0,1000][0,1000] 上的最小值。
思路
三分。这个 FFF 函数应该是单峰函数,不会证明但是肯定是[6],不然这题就是模拟退火了。
三分板子,但是是单谷函数,把求解的大于和小于换换就过了,注意多测和题目要求输出的是 F(ans)F(ans)F(ans)。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
引申——黄金分割优化法
注意到,三分每次操作都要调用两次 f()f()f() ,如果调用代价很高那么就容易炸掉。
那么要优化调用,我们就要使用黄金分割优化法。
首先,这是黄金分割比:
ϕ=5−12\phi=\frac{\sqrt{5}-1}{2} ϕ=25 −1
黄金分割有一个性质:
考虑一根线段 ABABAB ,其中 CCC 是 ABABAB 靠 AAA 的黄金分割点,DDD是靠 BBB 的黄金分割点。
那么对于线段 CBCBCB ,DDD 此时就是 CBCBCB 的靠 CCC 的黄金分割点。反之亦然。
容易想到,利用这个性质,可以取 lmidlmidlmid 和 rmidrmidrmid 为当前区间的黄金分割点,那么之后每次都可以复用一个点,降低了调用 fff 的次数。
取黄金分割点的公式:
lmid=ϕl+(1−ϕ)rrmid=(1−ϕ)l+ϕrlmid=\phi l + (1-\phi)r \\ rmid=(1-\phi)l+\phi r lmid=ϕl+(1−ϕ)rrmid=(1−ϕ)l+ϕr
只需要在开始时计算这两个点对应的 fff 值 lanslanslans 和 ransransrans。
之后在每次循环中,如果 l=lmidl=lmidl=lmid,就令 lmid=rmidlmid=rmidlmid=rmid,lans=ranslans=ranslans=rans 然后再次计算一个 rmidrmidrmid;反之亦然。
全文完。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
引用
OI Wiki - 三分法
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1. 也可以是单谷函数的最小值。当求单谷函数最小值时,只需要把后文中提到的求解方式里的大于改成小于小于改成大于即可。若无特殊说明,本文中只讨论单峰函数。 ↩︎
2. 不一定是函数,只要是满足单峰/单谷性质的问题都可以这样求解。 ↩︎
3. https://oi-wiki.org/basic/binary/#引入“为什么不通过求导函数的零点来求极值点?” ↩︎
4. 实际上为了减少误差,一般 epsepseps 会比题目要求小 333 ~ 444 个数量级,例如题目要求精度为 10−510^{-5}10−5 时一般 epsepseps 会设置为 10−810^{-8}10−8 左右。 ↩︎
5. 不管取哪个点三分都能运行(在端点上除外),但是会显著影响到三分的运行速度。一般除非需要使用黄金分割优化,否则在中点偏移一个极小值的效率最高。当然,也有一些题目必须取三等分点——这种要求常见于几何题目,尤其是平面直角坐标系上。 ↩︎
6. 其实也可能退化为一个一次函数,但是题目规定了求最小值的范围,所以没有这个问题。 ↩︎