P1024 一元三次方程求解 牛顿迭代+盛金公式+二分+勘根定理

P1024 一元三次方程求解

传送门

题目描述

有形如:ax^3+bx^2+cx^1+dx^0=0 这样的一个一元三次方程。给出该方程中各项的系数(a,b,c,d均为实数),并约定该方程存在三个不同实根(根的范围在-100至100之间),且根与根之差的绝对值1。要求由小到大依次在同一行输出这三个实根(根与根之间留有空格),并精确到小数点后2位。

提示:记方程f(x)=0,若存在2个数x1x2,且x1<x2f(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
",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
",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
",r);
                break;//跳出能省点时间
            }
        }
    }
    return 0;
}

其实本题由于数据比较少,好像暴力枚举也能过(因为精度实在是太小了!0.01),但是我就不写了,比较简单。

原文地址:https://www.cnblogs.com/Mangata/p/13520980.html