题解:洛谷 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

【核心思想】

  1. 问题分析:给定一个一元三次方程 \(ax^3+bx^2+cx+d=0\),系数为实数,已知该方程存在三个不同的实根,且根均在 \([-100, 100]\) 内,任意两个根之差的绝对值至少为 \(1\)。需要按从小到大的顺序输出这三个根,精确到小数点后 \(2\) 位。由于根的范围和间隔已知,可以利用零点存在定理(若 \(f(x_1) \cdot f(x_2) < 0\),则在 \((x_1, x_2)\) 内至少有一个根),结合浮点数二分法逐个定位每个根。

  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}\) 或更小即可保证四舍五入后的正确性。
  3. 关键步骤

    • 读入系数:读取实数 \(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\) 即可)。
    • 输出:按找到的顺序(即从小到大)输出三个根,每个保留两位小数。
  4. 时间/空间复杂度

    • 时间复杂度:\(O(200 \times \log(\text{精度}))\),常数级,因为区间总数固定(200),每次二分约 20 次迭代。
    • 空间复杂度:\(O(1)\)
  5. 浮点二分 + 零点定理的核心思想

    • 区间划分:利用根之间间隔 \(\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
posted @ 2026-09-02 21:40  团爸讲算法  阅读(41)  评论(0)    收藏  举报