P1024 一元三次方程求解 牛顿迭代+盛金公式+二分+勘根定理
P1024 一元三次方程求解
题目描述
有形如:ax^3+bx^2+cx^1+dx^0=0 这样的一个一元三次方程。给出该方程中各项的系数(a,b,c,d均为实数),并约定该方程存在三个不同实根(根的范围在-100至100之间),且根与根之差的绝对值≥1。要求由小到大依次在同一行输出这三个实根(根与根之间留有空格),并精确到小数点后2位。
提示:记方程f(x)=0,若存在2个数x1和x2,且x1<x2,f(x1)×f(x2)<0,则在(x1,x2)之间一定有一个根。
输入格式
一行,4个实数A,B,C,D。
输出格式
一行,3个实根,并精确到小数点后2位。
输入输出样例
输入 #1
1 -5 -4 20
输出 #1
-2.00 2.00 5.00
解题思路1:可以通过盛金公式分开讨论求解:盛金公式博客:传送门
AC代码:
#include<iostream> #include<cmath> #include<cstdio> #include<set> #include<queue> #define EPS 0.001 using namespace std; double a,b,c,d;int main(void) { ios::sync_with_stdio(false); cin>>a>>b>>c>>d; priority_queue<double,vector<double>,greater<double> > q; double A,B,C; A=b*b-3*a*c; B=b*c-9*a*d; C=c*c-3*b*d; double derta=B*B-4*A*C; double K=B/A; double x1,x2,x3; if(A==B&&B==0) { x1=x2=x3=(-b)/(3*a); } else if(derta==0) { x1=-b/a+K; x2=x3=-K/2; } else if(derta<0) { double T=(2*A*b-3*a*B)/(2*A*sqrt(A)); double theta=acos(T); double xt=theta/3.0; x1=(-1*b-2*sqrt(A)*cos(xt))/(3.0*a); x2=(-1*b+sqrt(A)*(cos(xt)-sqrt(3)*sin(xt)))/(3*a); x3=(-1*b+sqrt(A)*(cos(xt)+sqrt(3)*sin(xt)))/(3*a); } q.push(x1); q.push(x2); q.push(x3); while(q.size()!=1) { printf("%.2lf ",q.top()); q.pop(); } printf("%.2lf\n",q.top()); q.pop(); return 0; }
解法思路2:可以通过牛顿迭代,我们知道这个三次函数有三个解,那么就会有两个波峰,通过对导函数的学习我们知道
波峰的时候当f'(x)=0,我们就能找到两个波峰的横坐标,我们通过对左边波峰的横坐标的左边进行牛顿迭代,我们就能求出最左边的零点
我们通过右边波峰的横坐标的右边进行牛顿迭代,就能求出最右边的零点,然后对两个波峰之间的横坐标进行牛顿迭代,我们就能求出最中间的零点
附上大佬的牛顿迭代讲解:传送门
AC代码:
#include<iostream> #include<cmath> #include<cstdio> double a,b,c,d; using namespace std; #define EPS 0.001 double f(double x)//这里是原函数(^U^)ノ~YO { return a*x*x*x+b*x*x+c*x+d; } double ff(double x)//这是导函数( ̄▽ ̄)" { return 3*a*x*x+2*b*x+c; } double ND(double x)//这就是牛顿迭代((●'◡'●)) { while(fabs(f(x))>EPS) x-=f(x)/ff(x); return x; } int main(void) { cin>>a>>b>>c>>d; double derta=sqrt(4*b*b-12*a*c); double x1=(-2*b-derta)/(6*a);//左边波峰的横坐标 double x2=(-2*b+derta)/(6*a);//右边波峰的横坐标 printf("%.2lf %.2lf %.2lf\n",ND(x1-EPS),ND((x1+x2)/2),ND(x2+EPS)); }
解法思路3:二分法!,这也是此题的考点!(其实我开始二分也不会),在借鉴了大佬的博客之后,
我还是搞懂了此处二分的技巧(但是开始看到的时候就是想不出来%%%),因为零点的范围是[-100.0,100.0]
并且两根之间的距离大于等于1,并且题目给出了提示f(x1)*f(x2)<0,则在(x1,x2)之间必有零点(这个就是勘根定理),所以我们可以枚举每一个范围为1的区间
然后查看是否存在零点,然后找到三个输出就行。(因为必定有三个不同的实根)。
AC代码:
#include<iostream> #include<cmath> #include<cstdio> #include<queue> #define EPS 0.001 using namespace std; double a,b,c,d; double f(double x) { return a*x*x*x+b*x*x+c*x+d; } int main(void) { ios::sync_with_stdio(false); cin>>a>>b>>c>>d; int ans=0; for(int i=-100;i<100;++i) { double l=i;//这个范围就为1 double r=i+1.0; if(!f(l))//如果找到左零点 { ans++; printf("%.2lf ",l);//直接输出 } if(f(l)*f(r)<0)//勘根定理,发现在l和r范围有零点 { while(fabs(l-r)>EPS)//二分 { double mid=(l+r)/2; if(f(mid)*f(r)<0) l=mid; else r=mid; } if(ans<2) printf("%.2lf ",r); else { printf("%.2lf\n",r); break;//跳出能省点时间 } } } return 0; }
其实本题由于数据比较少,好像暴力枚举也能过(因为精度实在是太小了!0.01),但是我就不写了,比较简单。