這是洛谷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; }