W3 school 菜鸟教程 我要自学网 信息学奥赛NOI 花哥的博客 不逼自己一把,怎知自己有多优秀——校长语录

1238:一元三次方程求解

【原始题目】

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
1238:一元三次方程求解

【分析】

   依次搜索根的范围,找出三个根就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;
}

 

posted @ 2020-03-26 18:07  耍人  阅读(1255)  评论(0编辑  收藏  举报