题解 P3382 【【模板】三分法】

NaCly_Fish

2018-12-26 18:49:06

Solution

看见有很多大佬用求导+二分做的,学过高数都知道,其实求导不难。 那没学过高数的怎么办? 这里提供一种用导数本质来计算的方法。 在看导数的计算之前,我们先想一下斜率的计算。 过 $(x_1,y_1),(x_2,y_2)$ 两点的直线斜率为: $$\text{Slope}=\frac{y_1-y_2}{x_1-x_2}$$ 而导数计算的就是过函数某一点切线的斜率。 在函数 $f(x)$ 一点 $(x,f(x))$ 上,如果向右移动一点点(设移动了 $\Delta x$),就到了 $(x+\Delta x,f(x+\Delta x))$。 当 $\Delta x$ 趋于 $0$ 时,计算过这两点直线的斜率,就是过 $(x,f(x))$ 这点的切线斜率了。 然后很容易就能得出,函数 $f(x)$ 的导函数 $f'(x)$ 可以这样计算: $$f'(x)=\lim\limits_{\Delta x\rightarrow0}\frac{f(x+\Delta x)-f(x)}{\Delta x}$$ 不过我们只用求导数的数值解,也就是近似值。所以只需要把 $\Delta x$ 设为一个很小的值就可以了。写成代码,就是: ```cpp double derivative(double x){ double dx = 1e-10; double dy = f(x+dx)-f(x); return dy/dx; } ``` 其中 `f` 就是计算函数值的。 对于求导的结果,也可以表示成一个函数变化的快慢(显然),导数值为正表示函数值在增加,导数值为负表示函数在减小。 那么,如果导数值为零,且左边导数值为正,右边导数值为负,那么我们就找到了最小值, 由于这题中保证给定的 $f(x)$ 在 $[l,r]$ 上一段单调增,一段单调减;也就是说,$f'(x)$ 在 $[l,r]$ 上是一部分大于 $0$,一部分小于 $0$,还有一个唯一的零点,正好对应最大值,于是就可以愉快的二分啦! 另外,这样做还有一个好处:不用计算导函数具体是多少,比本题其它求导做法适用性更广。 参考代码: ```cpp #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<cmath> #define eps (1e-10) using namespace std; double l,r; int n; double a[20]; inline double f(double x){ //秦九昭公式求多项式 double u[20]; u[n] = a[n]; for(int i=n-1;i>=0;--i) u[i] = u[i+1]*x+a[i]; return u[0]; } double check(double x){ double dx = eps; double dy = f(x+dx)-f(x); return dy/dx; } int main(){ scanf("%d",&n); scanf("%lf%lf",&l,&r); for(int i=0;i<=n;++i) scanf("%lf",&a[n-i]); double mid; while(r-l>eps){ mid = (l+r)/2; if(check(mid)>0) l = mid; else r = mid; } printf("%.5lf",mid); return 0; } ```