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)
题图如图所示:

但实际上根据 D D D 和 h h h 的变化会出现不同的情况。
当影子仅出现在地面时,如图所示:

根据三角形的相似性,有 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 。
当影子出现在墙壁时,如图所示:

根据相似三角形的性质,有
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)
题目测试样例如图:

容易想到若点 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)