题解:洛谷 P1024 一元三次方程求解
【题目来源】
洛谷:P1024 [NOIP 2001 提高组] 一元三次方程求解 - 洛谷
【题目描述】
有形如:\(ax^3+bx^2+cx+d=0\) 这样的一个一元三次方程。给出该方程中各项的系数(\(a,b,c,d\) 均为实数),并约定该方程存在三个不同实根(根的范围在 \(−100\) 至 \(100\) 之间),且根与根之差的绝对值 \(\ge 1\)。要求由小到大依次在同一行输出这三个实根(根与根之间留有空格),并精确到小数点后 \(2\) 位。
提示:记方程 \(f(x)=0\),若存在 \(2\) 个数 \(x_1\) 和 \(x_2\),且 \(x_1\lt x_2\),\(f(x_1)\times f(x_2)\lt 0\),则在 \((x_1,x_2)\) 之间一定有一个根。
【输入】
一行,\(4\) 个实数 \(a,b,c,d\)。
【输出】
一行,\(3\) 个实根,从小到大输出,并精确到小数点后 \(2\) 位。
【输入样例】
1 -5 -4 20
【输出样例】
-2.00 2.00 5.00
【核心思想】
-
问题分析:给定一个一元三次方程 \(ax^3+bx^2+cx+d=0\),系数为实数,已知该方程存在三个不同的实根,且根均在 \([-100, 100]\) 内,任意两个根之差的绝对值至少为 \(1\)。需要按从小到大的顺序输出这三个根,精确到小数点后 \(2\) 位。由于根的范围和间隔已知,可以利用零点存在定理(若 \(f(x_1) \cdot f(x_2) < 0\),则在 \((x_1, x_2)\) 内至少有一个根),结合浮点数二分法逐个定位每个根。
-
算法选择:
- 浮点数二分(实数二分):在 \([-100, 100]\) 区间内,以步长 \(1\) 遍历每个长度为 \(1\) 的区间 \([i, i+1]\)。由于根与根之间的间隔 \(\ge 1\),每个这样的区间最多包含一个根。检查区间端点函数值:
- 若 \(f(i)=0\),则 \(i\) 是一个根;
- 若 \(f(i) \cdot f(i+1) < 0\),则区间内存在一个根,对此区间进行二分查找,直到区间长度小于所需精度(如 \(10^{-4}\)),取中点或右端点作为根的近似值。
- 精度控制:由于输出要求精确到两位小数,二分时区间长度达到 \(10^{-3}\) 或更小即可保证四舍五入后的正确性。
- 浮点数二分(实数二分):在 \([-100, 100]\) 区间内,以步长 \(1\) 遍历每个长度为 \(1\) 的区间 \([i, i+1]\)。由于根与根之间的间隔 \(\ge 1\),每个这样的区间最多包含一个根。检查区间端点函数值:
-
关键步骤:
- 读入系数:读取实数 \(a, b, c, d\)。
- 定义函数 \(f(x)\):计算 \(ax^3+bx^2+cx+d\)。
- 遍历区间:从 \(x = -100\) 到 \(99\),步长为 \(1\):
- 若 \(|f(x)| < \epsilon\)(极小值,如 \(10^{-6}\)),则 \(x\) 是一个根,直接输出并计数。
- 若 \(f(x) \cdot f(x+1) < 0\),则区间 \((x, x+1)\) 内存在根,调用二分函数求根:
- 左端点 \(l = x\),右端点 \(r = x+1\)。
- 当 \(r - l > 10^{-4}\) 时,计算 \(mid = (l+r)/2\)。
- 若 \(f(mid) \cdot f(r) < 0\),则根在 \((mid, r)\) 内,更新 \(l = mid\);否则根在 \((l, mid)\) 内,更新 \(r = mid\)。
- 循环结束后,输出 \(l\) 或 \(r\)(两者差小于精度,取 \(l\) 即可)。
- 输出:按找到的顺序(即从小到大)输出三个根,每个保留两位小数。
-
时间/空间复杂度:
- 时间复杂度:\(O(200 \times \log(\text{精度}))\),常数级,因为区间总数固定(200),每次二分约 20 次迭代。
- 空间复杂度:\(O(1)\)。
-
浮点二分 + 零点定理的核心思想:
- 区间划分:利用根之间间隔 \(\ge 1\) 的性质,将 \([-100, 100]\) 划分为长度为 \(1\) 的小区间,保证每个区间至多一个根,从而避免处理多个根混合的情况。
- 符号判断定位:通过检查端点函数值符号变化,快速确定根所在的子区间,再使用二分法精确逼近,避免了全局搜索或求导的复杂性。
- 精度控制:浮点数二分通过不断缩小区间长度达到所需精度,实现简单且稳定,适合实数根求解。
- 适用于连续函数在给定区间内求单根或分离根的数值方法,是数值分析的基础应用。
【解题思路】

【算法标签】
普及- #浮点二分
【代码详解】
#include <bits/stdc++.h>
using namespace std;
// 全局变量:
// mark: 记录已找到的实根数量
// a, b, c, d: 三次方程的系数
// x: 临时变量(未使用)
int mark = 0;
double a, b, c, d, x;
/**
* 计算三次函数在x处的值
* @param x 自变量值
* @return 函数值
*/
double f(double x)
{
return a * x * x * x + b * x * x + c * x + d;
}
/**
* 使用二分法在区间[l, r]内查找函数的根
* @param l 区间左端点
* @param r 区间右端点
*/
void find(double l, double r)
{
// 当区间长度小于0.001时,输出右端点作为近似解
if (r - l < 0.001)
{
printf("%.2f ", r);
return;
}
double mid = (l + r) / 2;
// 如果中点恰好是根,直接输出
if (f(mid) == 0)
{
printf("%.2f ", mid);
return;
}
// 根据函数值符号变化决定搜索方向
if (f(l) * f(mid) < 0)
{
find(l, mid); // 在左半区间继续搜索
}
else
{
find(mid, r); // 在右半区间继续搜索
}
}
int main()
{
// 输入三次方程的系数
cin >> a >> b >> c >> d;
// 在[-100, 100]范围内搜索实根
for (double i = -100; i <= 100 && mark != 3; i++)
{
// 如果i恰好是根,直接输出
if (f(i) == 0)
{
printf("%.2f ", i);
mark++;
continue;
}
// 如果在[i, i+1]区间内有根,则进行二分查找
if (f(i) * f(i + 1) < 0)
{
find(i, i + 1);
mark++;
}
}
return 0;
}
#include <bits/stdc++.h>
using namespace std;
int mark = 0; // 这个变量似乎没有用到
double a, b, c, d, x; // 三次方程系数
// 三次函数 f(x) = ax³ + bx² + cx + d
double f(double x)
{
return a * x * x * x + b * x * x + c * x + d;
}
// 在区间[l, r]上使用二分法求根
double find(double l, double r)
{
double ans; // 这个变量没有用到
// 当区间长度足够大时继续二分
while (r - l >= 0.00001) // 精度为0.00001
{
double mid = (l + r) / 2;
// 判断根在哪个子区间
if (f(mid) * f(r) < 0) // 根在右半区间
{
l = mid; // 向右收缩
}
else // 根在左半区间
{
r = mid; // 向左收缩
}
}
return l; // 返回左端点作为近似根
}
int main()
{
// 输入三次方程的系数
cin >> a >> b >> c >> d;
// 在[-100, 100]区间内搜索根
for (double i = -100; i < 100; i++)
{
double y1 = f(i); // 区间左端点的函数值
double y2 = f(i + 1); // 区间右端点的函数值
// 如果f(i)等于0,i就是根
if (!y1) // 实际上应该用fabs(y1) < eps
{
printf("%.2f ", i);
}
// 如果f(i)和f(i+1)异号,区间内有根
if (y1 * y2 < 0)
{
printf("%.2f ", find(i, i + 1));
}
}
return 0;
}
【运行结果】
1 -5 -4 20
-2.00 2.00 5.00
浙公网安备 33010602011771号