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),但是我就不写了,比较简单。