1177 字
6 分钟
题解:P1024 [NOIP2001 提高组] 一元三次方程求解
题解:P1024 [NOIP2001 提高组] 一元三次方程求解
题目描述
给出一个形如 的一元三次方程,系数均为实数。已知该方程在 范围内存在三个不同的实根,且根与根之差的绝对值 。要求从小到大输出这三个实根,精确到小数点后 2 位。
方法一:求导分区间法(我的解法)
1. 算法思路
一元三次函数 的图像通常呈“N”字形或倒“N”字形。为了找到三个根,我们需要确定函数的单调区间。 单调性的转折点就是函数的极值点。
- 求导:对原函数求导得到二次函数 。
- 求极值点:令 ,利用求根公式 求出两个极值点 。
- 注意系数对应关系:。
- 划分区间:两个极值点将数轴(在题目范围内)划分为三个单调区间:
- 二分查找:由于题目保证有三个不同的实根,根据函数的连续性和单调性,这三个区间内必然各有一个根。我们只需要在每个区间内分别进行二分查找即可。
2. 代码实现
#include <iostream>#include <cmath>#include <algorithm>#include <iomanip>#include <vector>
using namespace std;
#define IOS ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
typedef pair<double, double> PDD;
double a, b, c, d;
// 计算函数值 f(x)double f(double x) { return a * x * x * x + b * x * x + c * x + d;}
int main() {
IOS;
cin >> a >> b >> c >> d;
// 1. 求导 f'(x) = 3ax^2 + 2bx + c double A = 3 * a; double B = 2 * b; double C = c;
// 2. 求极值点(导数为0的点) double delta = B * B - 4 * A * C; double x1 = (-B + sqrt(delta)) / (2 * A); double x2 = (-B - sqrt(delta)) / (2 * A);
// 确保 x1 < x2 if (x1 > x2) swap(x1, x2);
// 3. 划分三个单调区间 vector<PII> sections(3); sections[0] = {-100, x1}; sections[1] = {x1, x2}; sections[2] = {x2, 100};
// 4. 在每个区间内二分查找 for (int i = 0; i < 3; i++) { double l = sections[i].first; double r = sections[i].second;
// 当区间长度大于 1e-7 时继续二分,保证精度 while (r - l > 1e-7) { double mid = (l + r) / 2; // 零点存在定理:如果 f(mid) 和 f(l) 异号(或其中之一为0),说明根在左半边 // 注意:这里利用 f(l) 作为参考点判断符号变化 if (f(mid) * f(l) <= 0) { r = mid; } else { l = mid; } } // 输出结果,控制精度 cout << fixed << setprecision(2) << l << " "; }
return 0;}3. 复杂度分析
- 时间复杂度:,其中 是二分的次数(常数)。
- 空间复杂度:。
方法二:暴力枚举 + 二分(答案推荐解法)
1. 算法思路
利用题目给出的强力提示:“根与根之差的绝对值 ”。 这意味着,任意长度为 1 的区间 内,最多只可能有一个根。
- 枚举:从 到 枚举每一个整数 。
- 判断:检查区间 的左右端点函数值。
- 如果 ,则 是根。
- 如果 ,说明根在 之间。
- 二分:对于确定的区间 ,进行二分查找精确求根。
2. 代码片段
#include <iostream>#include <cmath>#include <algorithm>#include <iomanip>#include <vector>
using namespace std;
#define IOS ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
typedef pair<double, double> PDD;
double a, b, c, d;
// 计算函数值 f(x)double f(double x) { return a * x * x * x + b * x * x + c * x + d;}
int main() {
IOS;
cin >> a >> b >> c >> d;
for (int i = -100; i < 100; i++) { double y1 = f(i); double y2 = f(i + 1);
if (abs(y1) < 1e-9) { // 也就是 y1 == 0 cout << fixed << setprecision(2) << (double)i << " "; } else if (y1 * y2 < 0) { // 区间内有根 double l = i, r = i + 1; for (int k = 0; k < 100; k++) { // 二分 100 次 double mid = (l + r) / 2; if (f(mid) * f(l) <= 0) r = mid; else l = mid; } cout << fixed << setprecision(2) << l << " "; } }
return 0;}注:这种方法避开了繁琐的求导公式,且不用处理 等特殊情况,在比赛中容错率更高。
题解:P1024 [NOIP2001 提高组] 一元三次方程求解
https://qiqimora.cn/posts/题解p1024-noip2001-提高组-一元三次方程求解/