三分算法的简单应用

三分算法的简单应用

三分算法

二分法在单调函数上查找特定值或在有序数组中搜索目标,依赖于函数在区间内具有单调性,从而能够通过一次比较确定目标所在的半区间。

但很多问题需要求凹函数(或凸函数)的极值,凹函数在极值点两侧,函数的单调性相反:一侧递增,另一侧递减。此时用二分法取中点时,无法通过比较中点处的函数值与某个参考值来判断极值点位于中点的左侧还是右侧,因为函数值的大小关系并不能唯一确定方向。

三分法适用于求解凸性函数的极值问题 ,二次函数就是一个典型的单峰函数。 三分法与二分法一样,它会不断缩小答案所在的求解区间。二分法缩小区间利用的原理是函数的单调性,而三分法利用的则是函数的单峰性

至于为什么不用求导的方式求极值,是因为某些问题中需要求极值点的单峰函数并非一个单独的函数,而是多个函数进行特殊运算得到的函数,甚至是抽象函数或分段函数。此时在函数某些点上可能不可导。

例如求多个单调性不完全相同的一次函数组成的分段函数的最小值的最大值就不能用求导来求解。

三分算法求函数极值

设当前求解的区间为[l,r],令m1 =l+(r-l)/3,m2=r-(r-l)/3,接着计算这两个点的函数值f(m1),f(m2) 之后将两点中函数值更优的那个点称为好点(若计算最大值,则 f(m) 更大的那个点 就为好点,计算最小值同理),而函数值较差的那个点称为坏点。

可以证明,最优点与好点会与坏点同侧。如图所示:


f(m1)>f(m2),则m1是好点,而m2是坏点,因此最后的最优点会与m1 一起在m2的左侧,即我们的求解区间由[l,r]变为了[l,m2]。因此根据这个结论我们可以不停缩小求解区间,直至得出近似解。 与二分一样,我们可以指定三分的次数,或是根据r-l的值来终止算法。

以求上凸单峰函数的最大值为例,三分模板:

cpp 复制代码
double find(double l, double r) {
    const double dlt = 1e-10;
    while (r - l > dlt) {
        double m1 = l + (r - l) / 3;
        double m2 = r - (r - l) / 3;
        if (f(m1) < f(m2)) // m1比m2更靠近极值
            l = m1;
        else
            r = m2;
    }
    return f(l); // 极值点,这里l==r
}

三分算法也可以用在单调函数上,但求得的是 2 个端点的值。常出现的题目大致有求某个拥有具体解析式和定义域的函数的极值和优化枚举,其中的函数需要现场分析和推理,很考验个人的数学能力。

P1883 Error Curves - 洛谷

P1883 【模板】三分 / 函数 / [ICPC 2010 Chengdu R\] Error Curves - 洛谷](https://www.luogu.com.cn/problem/P1883) [1435:【例题3】曲线](http://ybt.ssoier.cn:8088/problem_show.php?pid=1435) 一堆二次函数组合在一起的叠加函数,因为 a \> 0 a\>0 a\>0 ,所以在`[0,1000]`可能单调递增,也可能是先单调递减,后单调递增。但无论是什么情况,这个函数在`[0,1000]`都可以看做凹函数或单调函数,然后对这个函数求最小值,可以尝试三分。 ```cpp #include using namespace std; const double dlt = 1e-11; vector a, b, c; int n; inline double f(double x) { double ans = a[1] * x * x + b[1] * x + c[1]; for (int i = 1; i < a.size(); i++) ans = max(ans, a[i] * x * x + b[i] * x + c[i]); return ans; } void ac() { cin >> n; a.resize(n + 1, 0); b = c = a; for (int i = 1; i <= n; i++) cin >> a[i] >> b[i] >> c[i]; double l = 0, r = 1000.0; while (r - l > dlt) { double m1 = f(l + (r - l) / 3); double m2 = f(r - (r - l) / 3); if (m1 <= m2) // 凹函数,m1比m2更接近极值 r = r - (r - l) / 3; else l = l + (r - l) / 3; } printf("%.4lf\n", f(r)); } int main() { // freopen("in.in", "r", stdin); int T = 1; cin >> T; while (T--) ac(); return 0; } ``` ### P5931 灯泡 - 洛谷 \[P5931 [清华集训 2015\] 灯泡 - 洛谷](https://www.luogu.com.cn/problem/P5931) [1438:灯泡](http://ybt.ssoier.cn:8088/problem_show.php?pid=1438) 题图如图所示: ![请添加图片描述](https://i-blog.csdnimg.cn/direct/2c9979a22c2b4bb29a92d753e1dd65cb.png) 但实际上根据 D D D 和 h h h 的变化会出现不同的情况。 当影子仅出现在地面时,如图所示: ![请添加图片描述](https://i-blog.csdnimg.cn/direct/ebb92b4dbbb14d7595c0bf7ada55ff43.png) 根据三角形的相似性,有 L D = L L + x = h H \\frac{L}{D}=\\frac{L}{L+x}=\\frac{h}{H} DL=L+xL=Hh ,交叉相乘再移项得 L = h H − h x L=\\frac{h}{H-h}x L=H−hhx 。随着 x x x 增大,当 x + L = D x+L=D x+L=D 时,影子 L L L 最长,否则影子就会有一部分到墙上。 影子 L L L 最长时, x = D − L = H − h H D x=D-L=\\frac{H-h}{H}D x=D−L=HH−hD 。也就是说,当影子在地面上时, x = H − h H D x=\\frac{H-h}{H}D x=HH−hD 。 当影子出现在墙壁时,如图所示: ![请添加图片描述](https://i-blog.csdnimg.cn/direct/a2bbc3449d8d40348282a78a279597e3.png) 根据相似三角形的性质,有 e d f g = D − x D \\frac{ed}{fg}=\\frac{D-x}{D} fged=DD−x 即 h − L + D − x H − L + D − x = D − x D \\frac{h-L+D-x}{H-L+D-x}=\\frac{D-x}{D} H−L+D−xh−L+D−x=DD−x 。 2 边同时乘以 2 个分母的乘积得: h D − L D + D 2 − D x = ( H + D − x ) ( D − x ) − L ( D − x ) hD-LD+D\^2-Dx=(H+D-x)(D-x)-L(D-x) hD−LD+D2−Dx=(H+D−x)(D−x)−L(D−x) 将要求的答案 L L L 移项到左边,其余移项到右边得: L = ( H + D − x ) ( D − x ) − h D − D 2 + D x − x = H D − H x + D 2 − D x − D x + x 2 − h D − D 2 + D x − x = H D − H x − D x + x 2 − h D − x = − x − ( H − h ) D x + H + D \\begin{aligned}L=\&\\frac{(H+D-x)(D-x)-hD-D\^2+Dx}{-x}\\\\=\&\\frac{HD-Hx+D\^2-Dx-Dx+x\^2-hD-D\^2+Dx}{-x}\\\\=\&\\frac{HD-Hx -Dx+x\^2-hD }{-x}\\\\=\&-x-\\frac{(H-h)D}{x}+H+D\\end{aligned} L====−x(H+D−x)(D−x)−hD−D2+Dx−xHD−Hx+D2−Dx−Dx+x2−hD−D2+Dx−xHD−Hx−Dx+x2−hD−x−x(H−h)D+H+D L = − x − ( H − h ) D x + H + D L=-x-\\frac{(H-h)D}{x}+H+D L=−x−x(H−h)D+H+D 是一个对勾函数,在 x ∈ \[ 0 , + ∞ \] x\\in \[0,+\\infty\] x∈\[0,+∞\] 是凹函数。 题目要求最大的影子长度,则函数 L L L 的定义域应为 \[ H − h H D , D \] \[\\frac{H-h}{H}D,D\] \[HH−hD,D\] ,在这个范围内, L L L 函数要么是单调函数,要么是凹函数,但无论是哪一个,都能使用三分算法求得极值。 \[P5931 [清华集训 2015\] 灯泡 - 洛谷](https://www.luogu.com.cn/problem/P5931) 和[1438:灯泡](http://ybt.ssoier.cn:8088/problem_show.php?pid=1438) 参考: ```cpp #include using namespace std; double f(double H, double h, double D, double x) { return (-x - ((D * (H - h)) / x) + H + D); } int main() { // freopen("in.in", "r", stdin); int T = 0; cin >> T; while (T--) { double H, h, D; cin >> H >> h >> D; double L = (H - h) * D / H, R = D; while (R - L > 1e-9) { double m1 = L + (R - L) / 3, m2 = R - (R - L) / 3; if (f(H, h, D, m1) < f(H, h, D, m2)) L = m1; // m2更靠近极值点 else R = m2; } printf("%.3lf\n", f(H, h, D, L)); } return 0; } ``` ### P2571 传送带 - 洛谷 [1439:【SCOI2010】传送带](http://ybt.ssoier.cn:8088/problem_show.php?pid=1439) \[P2571 [SCOI2010\] 传送带 - 洛谷](https://www.luogu.com.cn/problem/P2571) 题目测试样例如图: ![请添加图片描述](https://i-blog.csdnimg.cn/direct/a04fa1365de5477abc6c5e552b253612.png) 容易想到若点 E E E 固定,则 F F F 从 C C C 移动 到 D D D ,整体移动时间由长变短,再变长,这是一个单峰函数,此时可以用三分算法。 但因为 E E E 也是动点,所以也要对 E E E 进行枚举。但对 E E E 的轨迹用二分枚举的话就无法获得下一步行动,所以对 E E E 的轨迹也用三分。 这题可以求函数解析式,但最后的结果是个二元函数,同样是使用三分优化 E E E 和 F F F 的枚举,这里就不推导了。 参考程序: ```cpp #include using namespace std; using f64 = double; f64 ax, ay, bx, by, cx, cy, dx, dy, P, Q, R; bool eq(f64 x, f64 y) { return fabs(x - y) < 1e-9; } void midl(f64 &aim, f64 &x, f64 &y) { // 左端点收缩 aim = x + (y - x) / 3; }; void midr(f64 &aim, f64 &x, f64 &y) { // 右端点收缩 aim = y - (y - x) / 3; }; f64 dis(f64 x1, f64 y1, f64 x2, f64 y2) { // 计算2点间距 f64 x = x1 - x2, y = y1 - y2; return sqrt(x * x + y * y); } f64 tim(f64 x1, f64 y1, f64 x2, f64 y2) { // 计算费时 f64 dab = dis(x1, y1, ax, ay); f64 dcd = dis(x2, y2, dx, dy); f64 d12 = dis(x1, y1, x2, y2); return dab / P + dcd / Q + d12 / R; } f64 TCD(f64 x, f64 y) { // 枚举F点 f64 lx = cx, ly = cy, rx = dx, ry = dy; f64 mlx, mly, mrx, mry; while (!(eq(lx, rx) && eq(ly, ry))) { midl(mlx, lx, rx), midl(mly, ly, ry); midr(mrx, lx, rx), midr(mry, ly, ry); if (tim(x, y, mlx, mly) < tim(x, y, mrx, mry)) rx = mrx, ry = mry; // ml更接近最优解 else lx = mlx, ly = mly; } return tim(x, y, lx, ly); } f64 TAB() { // 枚举E点 f64 lx = ax, ly = ay, rx = bx, ry = by; f64 mlx, mly, mrx, mry; while (!(eq(lx, rx) && eq(ly, ry))) { midl(mlx, lx, rx), midl(mly, ly, ry); midr(mrx, lx, rx), midr(mry, ly, ry); if (TCD(mlx, mly) < TCD(mrx, mry)) // ml更接近最优解 rx = mrx, ry = mry; else lx = mlx, ly = mly; } return TCD(mlx, mly); } int main() { // freopen("in.in", "r", stdin); cin >> ax >> ay >> bx >> by; cin >> cx >> cy >> dx >> dy; cin >> P >> Q >> R; printf("%.2lf", TAB()); return 0; } ``` 这题解法很多,有模拟退火、暴力枚举、三分嵌套三分,这里只举例三分嵌套三分。 ## OJ参考 \[P1883 【模板】三分 / 函数 / [ICPC 2010 Chengdu R\] Error Curves - 洛谷](https://www.luogu.com.cn/problem/P1883) [1435:【例题3】曲线](http://ybt.ssoier.cn:8088/problem_show.php?pid=1435) \[P5931 [清华集训 2015\] 灯泡 - 洛谷](https://www.luogu.com.cn/problem/P5931) [1438:灯泡](http://ybt.ssoier.cn:8088/problem_show.php?pid=1438) [1439:【SCOI2010】传送带](http://ybt.ssoier.cn:8088/problem_show.php?pid=1439) \[P2571 [SCOI2010\] 传送带 - 洛谷](https://www.luogu.com.cn/problem/P2571)

相关推荐
2401_831920742 小时前
分布式系统安全通信
开发语言·c++·算法
WolfGang0073212 小时前
代码随想录算法训练营 Day17 | 二叉树 part07
算法
温九味闻醉2 小时前
关于腾讯广告算法大赛2025项目分析1 - dataset.py
人工智能·算法·机器学习
2401_877274242 小时前
从匿名管道到 Master-Slave 进程池:Linux 进程间通信深度实践
linux·服务器·c++
炽烈小老头2 小时前
【 每天学习一点算法 2026/03/23】数组中的第K个最大元素
学习·算法·排序算法
老鱼说AI2 小时前
大规模并发处理器程序设计(PMPP)讲解(CUDA架构):第四期:计算架构与调度
c语言·深度学习·算法·架构·cuda
汉克老师2 小时前
GESP5级C++考试语法知识(八、链表(三)循环链表)
c++·约瑟夫问题·循环链表·gesp5级·gesp五级
月落归舟2 小时前
帮你从算法的角度来认识数组------( 二 )
数据结构·算法·数组
阿贵---3 小时前
C++中的RAII技术深入
开发语言·c++·算法