一元三次方程求解

这是洛谷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<x2f(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;
}

 

 

posted @ 2022-01-25 23:24  zzzzzz2  阅读(828)  评论(0编辑  收藏  举报