题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5105
题目意思:给出一个6个实数:a, b, c, d, l, r。通过在[l, r]中取数 x,使得这个函数 f(x)= |a∗x3+b∗x2+c∗x+d| 最大。
我一开始做的时候,很天真的认为数据量这么小,一个一个试,暴力搜就肯定能得到答案啦。但是一个很严重的问题是,x 没有说是实数还是整数,所以枚举根本不可行。
于是就联想到应该使用高中求一元三次方程的方法来做。我当时是直接从 f(l), f(r),f(x1),f(x2)(x1、x2代表方程两个根)中选择最大的来输出,但是没有考虑是否在[l, r]范围内,竟然过了,那个开心啊。可想而知最后被 hack 了,连终评都到达不了,泪~~~
后来认认真真复习了具体求解方法,于是很正规地做:对ax^3 + bx^2 + c*x + d求导,得到一元二次方程 3ax^2 + 2bx + c,用delta(那个高中经常用到的三角形Δ)讨论根的情况,画出二次函数图象,根据单调性再画出原函数大致走向,再讨论根是否在区间[l, r]上来选择应该输出哪个f()。前前后后做了31次。那 30 次当中,知道自己很多的不足:
(1)竟然用 abs 来求浮点数绝对值,实际上是用 fabs!
(2)浮点数跟 0 比较是不可行的,要设定一个可接受的误差 eps,例如为1e-9,如果 < eps 就代表是 < 0 的。
(3)在做 delta 运算之前,漏了 a == 0(对应代码中 fabs(a) < eps) 的判断,这关乎到根存不存在的问题: 如果 b < eps(表示 b == 0),就代表无解啦,否则就有一个根 x = (-c) / (2b)
(4)最最致命的一点,就是按传统解题步骤做!今天在课上终于想明白为什么这样中规中矩做是不可行的,两天的努力没有白费了。试想下,这样做的情况非常多!
举个例子吧,假设我们已经知道 a != 0 啦,很自然地进行delta讨论啦,假设讨论到 delta > 0(其实delta = 0可以归为该类情况) 的情况,也就是有x1, x2两个根啦。然后根据在 x 坐标轴以上表示原函数递增,以下原函数递减,最后对应到原函数(ax^3 + bx^2 + c*x + d)画出大致走向,再根据 x 的位置来判断是否应该计算f(x)(包含f(x1), f(x2)),最后选出f(x), f(l),f(r)最大的那个输出。真的是复杂到不能再复杂啦。
我当时就在想为什么是错的,因为原函数不是单纯的 ax^3 + bx^2 + c*x + d,而是这个东西的绝对值!绝对值意味着这个东西的最小值或者最大值就是答案。而我常规做的,是非绝对值的情形,所以势必会漏了不容易觉察的情况。
那么最简单,最靠谱,最正确的方法就是,除了那种 a == 0 && b == 0 没根(不用算x)的情况,其他都需要算f(x),然后选出最大的,前提是 x 在 [l, r]之间。
这里补充说明一点,为什么我说的进行到 delta 判断中,可以将 delta < 0 与 delta >= 0 一并讨论,因为始终只会算出 x 在 [l, r]的 f(x),也就是不在或者不存在(对应delta < 0)的话就不计算(函数是返回-1),那么delta < 0 其实就是选择f(l)、 f(r)较大的一个输出。至于delta = 0,虽然只有一个根,即x1, x2相等,也不影响合并。
辛苦读者看我这篇长篇大论,不过通过这题真的学到很多,多想一个为什么一定会有所进步的!^_^ !
1 #include <iostream> 2 #include <cstdio> 3 #include <cstdlib> 4 #include <cstring> 5 #include <cmath> 6 using namespace std; 7 8 const double eps = 1e-9; 9 double a, b, c, d, l, r; 10 11 double get_max(double x, double y) 12 { 13 if (x > y) 14 return x; 15 return y; 16 } 17 18 double cal(double x) 19 { 20 if (x >= l && x <= r) // 前提是在[l, r] 之间 21 return fabs(a*x*x*x + b*x*x + c*x + d); 22 return -1; 23 } 24 25 int main() 26 { 27 while (scanf("%lf%lf%lf%lf%lf%lf", &a, &b, &c, &d, &l, &r) != EOF) 28 { 29 double fl = cal(l); 30 double fr = cal(r); 31 double ans = get_max(fl, fr); 32 33 if (fabs(a) < eps) // a == 0 34 { 35 if (fabs(b) < eps) // b == 0,无解 36 printf("%.2lf\n", ans); 37 else 38 { 39 double x = -c/(2*b); // 导数为一次函数,原函数为二次函数 40 printf("%.2lf\n", get_max(ans, cal(x))); 41 } 42 } 43 else // delta 环节 44 { 45 double delta = 4*b*b - 12*a*c; 46 delta = sqrt(delta); 47 double x1 = (-2*b - delta) / (6*a); 48 double x2 = (-2*b + delta) / (6*a); 49 double tans = get_max(cal(x1), cal(x2)); 50 printf("%.2lf\n", get_max(ans, tans)); 51 } 52 } 53 return 0; 54 }