1238:一元三次方程求解
时间限制: 1000 ms 内存限制: 65536 KB
提交数: 4113 通过数: 2017
【题目描述】
形如:ax3+bx2+cx+d=0ax3+bx2+cx+d=0 这样的一个一元三次方程。
给出该方程中各项的系数(a,b,c,da,b,c,d均为实数),并约定该方程存在三个不同实根(根的范围在−100−100至100100之间),且根与根之差的绝对值≥11。要求由小到大依次在同一行输出这三个实根(根与根之间留有空格),并精确到小数点后22位。
【输入】
一行,包含四个实数a,b,c,da,b,c,d,相邻两个数之间用单个空格隔开。
【输出】
一行,包含三个实数,为该方程的三个实根,按从小到大顺序排列,相邻两个数之间用单个空格隔开,精确到小数点后22位。
【输入样例】
1.0 -5.0 -4.0 20.0
【输出样例】
-2.00 2.00 5.00
【分析】
依次搜索根的范围,找出三个根就OK。原理(连续函数零点存在定理):对于继续函数y=f(x),若f(a)*f(b)<0,则函数在区间(a,b)上必存在零点,那我们的任务就是找到两端点值异号的区间。由于两根差的经验值大于等于1,所以,我们逐个扫描整点函数值的符号就必有线索。(为了防止漏网之鱼,我把扫描间隔改为0.5)。找到范围再定位根,那就简单了——二分法。若f(a)*f(b)<0,取m=(a+b)/2,求f(m)的值。三种情况:1、运气比较好,f(m)=0,那就不用继续了,直接得结果。2、f(m)*f(a)>0,则f(m)*f(b)<0,把根的范围缩小到(m,b),继续递归,在(m,b)中找根。3、f(a)*f(m)<0,说明(a,m)内有根,同2,递归在(a,m)找根。
注意:由于精度原因,符号的判定一般用把x>0改为x>1e-12,x<0改为x<-1e-12(也可以写-x>1e-12),其他情况视为x=0。这个问题在1058题中有说明。
AC代码:
//1238:一元三次方程求解 #include<iostream> #include<iomanip> using namespace std; int k; double ans[3],a,b,c,d,x0=-101; //x0是根所在区间的左界,根有可能是-100,所以x0必须小于-100 int f(double x) { double y; y=a*x+b; y=x*y+c; y=x*y+d;//写的有点复杂,顺便搞下秦九韶算法 if(-y>1e-12)return -1; else if(y>1e-12)return 1; else return 0; } //二分法找根,原理:连续函数y=f(x):f(a)*f(b)<0 ,则在(a,b)上必有一根 double key(double x,double y) { double m=(x+y)/2; if(y-x<0.0005||f(m)==0)return m;//精确到小数点后2位,最少多算一位,再送一次精度更高哈 if(f(x)*f(m)==1)return key(m,y); else return key(x,m); } int main(){ cin>>a>>b>>c>>d; for(double i=-100;i<=100;i+=0.5) if(f(i)==0) { ans[k++]=i; x0=i+0.5;//很重要,否则下次判断符号就错了 if(k==3)break; } else if(f(x0)*f(i)==1)x0=i; else { ans[k++]=key(x0,i); x0=i;//很重要,否则下次判断符号就错了 if(k==3)break; } for(int i=0;i<3;i++) cout<<fixed<<setprecision(2)<<ans[i]<<' '; return 0; }