博主主页:Yu·仙笙
专栏地址:洛谷千题详解
目录
--------------------------------------------------------------------------------------------------------------------------------
题目描述
有形如:a x^3 + b x^2 + c x + d = 0ax3+bx2+cx+d=0 这样的一个一元三次方程。给出该方程中各项的系数(a,b,c,da,b,c,d 均为实数),并约定该方程存在三个不同实根(根的范围在 -100−100 至 100100 之间),且根与根之差的绝对值 \ge 1≥1。要求由小到大依次在同一行输出这三个实根(根与根之间留有空格),并精确到小数点后 22 位。
提示:记方程 f(x) = 0f(x)=0,若存在 22 个数 x_1x1 和 x_2x2,且 x_1 < x_2x1<x2,f(x_1) \times f(x_2) < 0f(x1)×f(x2)<0,则在 (x_1, x_2)(x1,x2) 之间一定有一个根。
--------------------------------------------------------------------------------------------------------------------------------
输入格式
一行,4 个实数 a, b, c, d
--------------------------------------------------------------------------------------------------------------------------------
输出格式
一行,3 个实根,从小到大输出,并精确到小数点后 2 位。
--------------------------------------------------------------------------------------------------------------------------------
输入输出样例
输入 #1复制
1 -5 -4 20
输出 #1复制
-2.00 2.00 5.00
说明/提示
【题目来源】
NOIP 2001 提高组第一题
--------------------------------------------------------------------------------------------------------------------------------
解析:
导数+勘根定理+牛顿迭代.
先对函数求导,f'(x)=3ax^2+2*bx+c.
然后直接求根公式求f'(x)=0的点,也就是函数极点.
(我们可以顺便求一下凸形函数极值hhh)
这题保证有三个不定根,所以有两个单峰.
我们分别设这两个点为p,q.
然后显然的必有三个根在[-100,p),[p,q],(q,100]三个区间内 (两极点间必定存在零点,勘根定理).
然后用神奇的牛顿迭代法多次迭代就好了.
证明请自行百度,本蒟蒻只能感性的认识orz.
--------------------------------------------------------------------------------------------------------------------------------
C++源码:
#include<iostream>
#include<cstdio>
#include<cmath>
#define eps 1e-4
using namespace std;
double x1,x2,x3,a,b,c,d;
double f(double x){return a*x*x*x+b*x*x+c*x+d;}
double df(double x){return 3*a*x*x+2*b*x+c;}
double slove(double l,double r)
{
double x,x0=(l+r)/2;
while(abs(x0-x)>eps)
x=x0-f(x0)/df(x0),swap(x0,x);
return x;
}
int main()
{
cin>>a>>b>>c>>d;
double p=(-b-sqrt(b*b-3*a*c))/(3*a);
double q=(-b+sqrt(b*b-3*a*c))/(3*a);
x1=slove(-100,p),x2=slove(p,q),x3=slove(q,100);
printf("%.2lf %.2lf %.2lf",x1,x2,x3);
return 0;
}
--------------------------------------------------------------------------------------------------------------------------------
C++源码2:
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
double f(double x,double a,double b,double c,double d)//用于计算三次函数值
{
return a*x*x*x+b*x*x+c*x+d;
}
double solve(double a,double b,double c,double d,double x0,double x1)//割线法求解
{
double xx[2]={x0,x1};//用xx数组滚动存储结果
int flag=0;
while(abs(xx[0]-xx[1])>1e-4)//控制精度
{
xx[flag]=xx[flag]-f(xx[flag],a,b,c,d)/((f(xx[flag],a,b,c,d)-f(xx[flag^1],a,b,c,d)))*(xx[flag]-xx[flag^1]);
flag^=1;
}
return xx[0];
}
double a,b,c,d,x[3]={-1000,-1000,-1000};
int tot;
bool check(double xx)//判断是否为新的解
{
bool flag=0;
for(int i=0;i<3;++i)flag|=abs(xx-x[i])<0.5;
return !flag;
}
int main()
{
scanf("%lf%lf%lf%lf",&a,&b,&c,&d);
for(double i=-100;i<=100;i+=0.5)//注意这里以0.5为步长枚举。我一开始步长为一,结果有一个点答案有1.0,2.0,直接除以零GG
{//当然你也可以在除以零时特判,但我懒得写了
double xx=solve(a,b,c,d,i,i+0.5);
if(check(xx))x[tot++]=xx;
}
sort(x,x+3);//不要忘记从小到大排序 //我太懒了QAQ
for(int i=0;i<3;++i)printf("%.2lf ",x[i]);
}
--------------------------------------------------------------------------------------------------------------------------------
Java源码:
import java.util.*; // P1024 [NOIP2001 提高组] 一元三次方程求解
public class Main{ // 暴力枚举
public static void main(String[] args){
Scanner rd=new Scanner(System.in);
double a=rd.nextDouble();
double b=rd.nextDouble();
double c=rd.nextDouble();
double d=rd.nextDouble();
for(double i=-100;i<=100;i+=0.001) { //数据范围从-100到100
double j=i+0.001;
double y1=a*i*i*i+b*i*i+c*i+d;
double y2=a*j*j*j+b*j*j+c*j+d;
if(y1>=0&&y2<=0||y1<=0&&y2>=0) { 若存在两个数y1,y2且y1<y2,f(y1)*f(y2)<0 则方程解肯定在y1~y2范围内 基本数学原理
double x=(i+j)/2;
System.out.print(String.format("%.2f", x)+" ");
}
}
}
}
--------------------------------------------------------------------------------------------------------------------------------