这是洛谷P1024,先上题目。
题目描述
有形如:a x^3 + b x^2 + c x + d = 0 这样的一个一元三次方程。给出该方程中各项的系数(a,b,c,d均为实数),并约定该方程存在三个不同实根(根的范围在 -100 至 100之间),且根与根之差的绝对值 ≥1。要求由小到大依次在同一行输出这三个实根(根与根之间留有空格),并精确到小数点后2位。
提示:记方程 f(x) = 0f(x)=0,若存在 22 个数 x_1x1 和 x_2x2,且 x_1 < x_2x1<x2,f(x_1) \times f(x_2) < 0f(x1)×f(x2)<0,则在 (x_1, x_2)(x1,x2) 之间一定有一个根。
输入格式
一行,4 个实数 a, b, c, da,b,c,d。
输出格式
一行,3个实根,从小到大输出,并精确到小数点后 2位。
输入输出样例
输入 #1
1 -5 -4 20
输出 #1
-2.00 2.00 5.00
开始毫无头绪,看了标签后,发现是二分,只要找到一个大于零的数,再找到一个小于零的数,分别作为最左值和最右值,二分就能得答案,但问题又出现了,一元三次方程最多有三个解,即使找到了大于和小于零的数,也无法排除其他部分,最开始我想到的是在其余部分再做寻找,直到找到三个解的范围,但这样不就和从前往后捋一样了吗?于是我便从网上查了一下一元三次方程,结果根据导数可以确定一元三次方程单调递增、递减的范围,而每个范围中的解至多一个,所以在每个范围中二分求解即可。还有的小问题在代码中注释。
下面上代码。
#include<iostream> #include<cstdio> #include<cmath> using namespace std; double a,b,c,d; double san(double n){ return a*n*n*n+b*n*n+c*n+d; } double derta(double x,double y,double z){ return y*y-4*a*c; } void erfen(double l,double r){ double mid=(l+r)/2; while(r-l>0.004){//最左值和最右值差0.004是因为要保留两位小数 if(san(mid)>0){ r=mid; } else if(san(mid)<0){ l=mid; } else{ break; } mid=(l+r)/2; } printf("%.2lf ",mid); } //两个二分是因为不确定单调递增还是单调递减 void erfen2(double l,double r){ double mid=(l+r)/2; while(r-l>0.004){ if(san(mid)>0){ l=mid; } else if(san(mid)<0){ r=mid; } else{ break; } mid=(l+r)/2; } printf("%.2lf ",mid); } int main(){ cin>>a>>b>>c>>d; double l1=-100,l2=-100,l3=-100,r1=100,r2=100,r3=100; if(derta(3*a,2*b,c)<=0){//f(x)=ax^3+bx^2+cx+d的导数是f`(x)=3ax^2+2bx+c double l=-100.0,r=100.0; if(a>0)erfen(l,r);//a的正负决定递增递减 else erfen2(l,r); } else{ double l=-100.0,r=(-2*b-sqrt(derta(3*a,2*b,c)))*1.0/(6*a); if(san(r)>=0){ if(a>0)erfen(l,r); else erfen(l,r); } l=(-2*b-sqrt(derta(3*a,2*b,c)))*1.0/(6*a); r=(-2*b+sqrt(derta(3*a,2*b,c)))*1.0/(6*a); if(san(r)<0&&san(l)>0){//单调递增和单调递减分界处为解时,只取一次 if(a>0)erfen2(l,r); else erfen(l,r); } l=(-2*b+sqrt(derta(3*a,2*b,c)))*1.0/(6*a); r=100.0; if(san(l)<=0){ if(a>0)erfen(l,r); else erfen2(l,r); } } return 0; }