P1024 [NOIP2001 提高组] 一元三次方程求解( 普及- ) 题解

发布时间 2023-12-08 22:40:47作者: BadBadBad__AK

题目传送门

思路:

1

可以直接暴力

2

二分搜索答案

3

盛金公式
一元三次方程:\(ax^3+cx^2+d=0\)
重根判别公式:
\(A=b^2-3ac\)
\(B=bc-9ad\)
\(C=c^2-3bd\)
\(A=B=0\)时,\(X1=X2=X3= -b/3a= -c/b = -3d/c\)

4

牛顿迭代法:
对于一个已知的x值,每一次根据函数在这一点的导数,把x移动到,切线与x轴相交的地方。
\(n+1]=x[n]-f(x)/f'(x)\)可以证明结果会趋近于函数的一个解。

5

勘根定理:
设函数f在闭区间\([a, b]\)中连续,且函数值\(f(a)\)\(f(b)\)异号(即,一为正一为负)。则在区间\((a, b)\)中找到一个数c,使得\(f(c) = 0\)(即,\(c\)为函数\(f\)的根)。

Code:

暴力

#include <bits/stdc++.h>
using namespace std;
int main()
{
	double a,b,c,d;
	cin>>a>>b>>c>>d;
	for(double i=-100;i<=100;i+=0.001)//因为根的范围在-100~100之间,直接枚举-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 and y2<=0||y1<=0 and y2>=0)
		{
			double x=(i+j)/2;
			printf("%.2lf ",x);
		}
	}
}

二分搜索

#include<bits/stdc++.h>
using namespace std;
double a,b,c,d;
double fc(double x)
{
	return a*x*x*x+b*x*x+c*x+d;
}
int main()
{
	double l,r,m,x1,x2;
	int s=0,i;
	cin>>a>>b>>c>>d;
	for (i=-100;i<100;i++)
	{
		l=i; 
		r=i+1;
		x1=fc(l); 
		x2=fc(r);
		if(!x1)//判断左端点,是零点直接输出。
		{
			printf("%.2lf ",l); 
			s++;
		}
		//不能判断右端点,会重复。
		if(x1*x2<0)//区间内有根。
		{
			while(r-l>=0.001)//二分控制精度。
			{
				m=(l+r)/2;  //middle
				if(fc(m)*fc(r)<=0) 
					l=m; 
				else 
					r=m;//计算中点处函数值缩小区间。
			}
			printf("%.2lf ",r);//输出右端点。
			s++;
		}
		if (s==3) 
			break;//找到三个就退出大概会省一点时间
	}
	return 0;
}

盛金公式

#include <bits/stdc++.h>
using namespace std;
int main()
{
	double a,b,c,d;
	double as,bs,t,si;
	double x1,x2,x3;
	cin>>a>>b>>c>>d;
	as=b*b-3*a*c;
	bs=b*c-9*a*d;
	t=(2*as*b-3*a*bs)/(2*sqrt(as*as*as));
	si=acos(t);
	x1=(-b-2*sqrt(as)*cos(si/3))/(3*a);
	x2=(-b+sqrt(as)*(cos(si/3)+sqrt(3)*sin(si/3)))/(3*a);
	x3=(-b+sqrt(as)*(cos(si/3)-sqrt(3)*sin(si/3)))/(3*a);
	cout<<fixed<<setprecision(2)<<x1<<" ";
	cout<<fixed<<setprecision(2)<<x3<<" ";
	cout<<fixed<<setprecision(2)<<x2<<" ";
	return 0;
}

牛顿迭代法

#include <bits/stdc++.h>//不要问为什么我这个蒟蒻写的出来,问就是抄的
using namespace std;
struct func3 {
	double a, b, c, d;
	func3(double A = 0, double B = 0, double C = 0, double D = 0) {
		a = A;
		b = B;
		c = C;
		d = D;
	}
	double operator()(double x) {
		return ((a * x + b) * x + c) * x + d;
	}
	double dvt(double x) {
		return (3.0 * a * x + 2.0 * b) * x + c;
	}
};
void func3solve(func3 f, double st, double& val, double& sol) {
	for (int i = 1; !(abs(f(st)) < 1e-6) && i <= 100; i++) {
		st = st - f(st) / f.dvt(st);
	}
	val = f(st);
	sol = st;
}
double fix2(double sol) {
	return (double)int(sol * 100.0 + (sol > 0 ? 0.5 : -0.5)) / 100.0;
}
set<double>solutions;
int main() {
	double a, b, c, d;
	scanf("%lf%lf%lf%lf", &a, &b, &c, &d);
	func3 f(a, b, c, d);
	for (double i = -100.0; i <= 100.0; i += 0.5) {
		double val, sol;
		func3solve(f, i, val, sol);
		sol = fix2(sol);
		if (abs(val) < 1e-6 && solutions.find(sol) == solutions.end())
			solutions.insert(sol);
	}
	for (set<double>::iterator it = solutions.begin(); it != solutions.end(); it++) {
		double x = (*it);
		printf("%.2lf ", x);
	}
	return 0;
}

勘根定理

#include <bits/stdc++.h>
using namespace std;
double a,b,c,d;
double f(double x)
{
	return (x*x*x*a+x*x*b+x*c+d);//计算一元三次方程的值
}
int main()
{
	double x1,x2,xx;
	scanf("%lf%lf%lf%lf",&a,&b,&c,&d);//都是实数,不一定非是整数 
	for(double i=-100;i<=100;i++) {//分别枚举
		x1=i;x2=i+1;
		if(f(x1)==0)//说明为零,直接输出
			printf("%.2lf ",i);
		if(f(x1)*f(x2)<0) {//小于零说明有零点,即说明有解。
			while(x2-x1>=0.001) {//范围 
				xx=(x1+x2)/2;//二分
				if(f(x1)*f(xx)<=0)//判断零点在x1到xx还是xx到x2
					x2=xx;//更新 
				else
					x1=xx;
			}
			printf("%.2lf ",x1);//输出 
		}
	}
}